Skip to content

Instantly share code, notes, and snippets.

@philippe86220
Last active December 11, 2025 08:47
Show Gist options
  • Select an option

  • Save philippe86220/c408b16c53889c3341a6b18e982dc7a0 to your computer and use it in GitHub Desktop.

Select an option

Save philippe86220/c408b16c53889c3341a6b18e982dc7a0 to your computer and use it in GitHub Desktop.
Calculatrice rétro

1. Les 4 variables d’état principales

Dans la vue :

@State private var display = "0"
@State private var previousValue: Double? = nil
@State private var currentOperator: Operateur? = nil
@State private var isEnteringNewNumber = true

display

  • C’est le texte affiché à l’écran (par exemple "0", "123", "3.14").
  • Toute la saisie utilisateur (chiffres, point) modifie ``display`.
  • C’est aussi à partir de display qu’on calcule la valeur numérique courante.

previousValue

  • C’est la mémoire de la calculatrice.
  • Elle stocke le premier opérande quand tu tapes quelque chose comme :
    12 + 5 =
    • previousValue sera12,
    • display contiendra 5 juste avant de faire le calcul. Quand on enchaîne des opérations (2 + 3 + 4 =), previousValue est mise à jour à chaque étape.

currentOperator

  • C’est l’opérateur actuellement choisi : .addition, .soustraction, .multiplication, .division.
  • Il est mis à jour quand tu appuies sur +, -, ×, ÷.
  • Il est utilisé au moment de tapOperator (chaînage) et tapEquals.

isEnteringNewNumber

  • Booléen qui dit si on est en train :
    • de commencer un nouveau nombre (true),
    • ou de continuer à taper le nombre courant (false).
  • Exemple :
    • Au début : display = "0", isEnteringNewNumber = true.
    • Tu tapes 7 → on remplace "0" par "7", puis isEnteringNewNumber = false.
    • Tu appuies sur + → la prochaine fois que tu tapes une touche chiffre, on doit repartir à zéro pour le nouvel opérande → isEnteringNewNumber = true.

2. Helper : valeur numérique et format

currentValue

private var currentValue: Double {
    Double(display) ?? 0
}
  • Convertit le texte affiché (display) en Double.
  • Si la conversion échoue (théoriquement ça ne devrait pas arriver), ça retourne 0.
  • C’est la valeur numérique utilisée dans tous les calculs.

format(_ value: Double)

private func format(_ value: Double) -> String {
    if value.rounded() == value {
        return String(Int(value))
    } else {
        return String(value)
    }
}
  • Sert à rendre le résultat plus joli :
    • Si la valeur est un entier (5.0) → on affiche "5".
    • Sinon on garde le nombre avec décimales (3.14).
  • Ça évite les affichages du genre 2.0 pour 2.

3. Saisie des chiffres : tapDigit(_:)

private func tapDigit(_ digit: String) {
    if isEnteringNewNumber || display == "0" {
        display = digit
    } else {
        display.append(digit)
    }
    isEnteringNewNumber = false
}

Rôle :

  • Gérer la saisie d’un chiffre ("0" à "9"). Logique :
  • Si on commence un nouveau nombre (isEnteringNewNumber == true) **ou** que l’écran montre "0"` :
    • On remplace le contenu par le nouveau chiffre (display = digit).
    • Exemple : départ "0", tu tapes "7" → écran "7".
  • Sinon :
    • On concatène le chiffre à la fin (display.append(digit)).
    • Exemple : écran "12", tu tapes "3""123".

Puis :

isEnteringNewNumber = false
  • On indique qu’on est maintenant en train de continuer la saisie de ce nombre (et non d’en commencer un nouveau).

4. Saisie du point décimal : tapDecimal()

private func tapDecimal() {
    if isEnteringNewNumber {
        display = "0."
        isEnteringNewNumber = false
    } else if !display.contains(".") {
        display.append(".")
    }
}

Cas 1 : on commence un nouveau nombre

  • Si isEnteringNewNumber est true :
    • On affiche "0." (comme sur une vraie calculatrice si tu appuies d’abord sur .).
    • Puis isEnteringNewNumber = false.

Cas 2 : on est en train de taper un nombre

  • Si on n’est pas en début de nombre :
    • On vérifie que display ne contient pas déjà un point (.).
    • S’il n’y en a pas, on l’ajoute à la fin (display.append(".")).

🎯 Objectif : empêcher deux points décimaux dans le même nombre.


5. Choix d’un opérateur : tapOperator(_:)

private func tapOperator(_ op: Operateur) {
    let current = currentValue
    
    if let prev = previousValue, let currentOp = currentOperator, !isEnteringNewNumber {
        let result = currentOp.appliquer(prev, current)
        previousValue = result
        display = format(result)
    } else {
        previousValue = current
    }
    
    currentOperator = op
    isEnteringNewNumber = true
}

C’est là que ça devient intéressant 😄

5.1. On lit la valeur courante

let current = currentValue
  • On convertit ce qui est affiché en nombre (Double).

5.2. Deux comportements possibles

Cas A : il y a déjà un calcul en cours

if let prev = previousValue, let currentOp = currentOperator, !isEnteringNewNumber {
    let result = currentOp.appliquer(prev, current)
    previousValue = result
    display = format(result)
}

Ce if vérifie trois choses :

  1. previousValue existe déjà.
    → on a déjà un premier nombre mémorisé,

  2. currentOperator existe.
    → on a déjà un opérateur en attente,

  3. !isEnteringNewNumber.
    → on vient réellement de taper un nouveau nombre, on ne fait pas juste changer d’opérateur.

Dans ce cas :

  • On effectue : result = currentOp.appliquer(prev, current).

    • Par exemple : tu as tapé 2 + 3, puis tu appuies sur + encore → on calcule 2 + 3.
  • On met à jour :

  • previousValue = result (pour pouvoir enchaîner avec le suivant),

  • display = format(result) pour afficher le résultat intermédiaire.

➡️ C’est ce qui permet de faire :
2 + 3 + 4 + 5 =
et d’obtenir la somme en enchaînant les opérations.


Cas B : c’est le premier opérateur

else {
    previousValue = current
}
  • Si on n’a pas encore de previousValue ou d’opérateur,
  • Alors on se contente de mémoriser la valeur courante comme premier opérande.

5.3. Mise à jour de l’opérateur et de l’état de saisie

currentOperator = op
isEnteringNewNumber = true
  • currentOperator = op :
    • On mémorise l’opérateur choisi maintenant (+,-, ×, ÷).
  • isEnteringNewNumber = true :
    • Le prochain chiffre tapé correspondra à un nouveau nombre (le second opérande).

6. Appui sur “=” : tapEquals()

private func tapEquals() {
   let current = currentValue
   
   if let prev = previousValue, let op = currentOperator {
       let result = op.appliquer(prev, current)
       display = format(result)
       previousValue = nil
       currentOperator = nil
       isEnteringNewNumber = true
   }
}

Étapes :

  1. On lit la valeur courante (current).
  2. On vérifie qu’on a :
  • un previousValue (premier opérande),
  • un currentOperator (opération en attente).

Si c’est le cas :

  • On calcule : result = op.appliquer(prev, current) (par exemple : 12 + 5, 3 × 4, etc.)
  • On affiche le résultat : display = format(result).

Puis on reset l’état :

  • previousValue = nil
  • currentOperator = nil
  • isEnteringNewNumber = true (pour que la prochaine saisie remplace le résultat).

🎯 Conclusion :
tapEquals termine l’opération en cours et remet la calculatrice dans un état “prête pour un nouveau calcul”.


7. Réinitialisation : tapClear()

private func tapClear() {
    display = "0"
    previousValue = nil
    currentOperator = nil
    isEnteringNewNumber = true
}

Cette fonction remet toute la calculatrice à zéro :

  • écran : "0",
  • aucune valeur mémorisée,
  • aucun opérateur en attente,
  • prochaine saisie = nouveau nombre.

8. Changer le signe : toggleSign()

private func toggleSign() {
   let value = currentValue
   let newValue = -value
   display = format(newValue)
   isEnteringNewNumber = true
}
  • On lit la valeur actuelle.
  • On calcule son opposé (-value).
  • On l’affiche.
  • On se met en mode “nouveau nombre” pour que la prochaine saisie remplace ce résultat.

9. Pourcentage : applyPercent()

private func applyPercent() {
    let value = currentValue
    let newValue = value / 100.0
    display = format(newValue)
    isEnteringNewNumber = true
}
  • Transforme la valeur actuelle en pourcentage (ex. 500.5).
  • Met à jour l’affichage.
  • Prépare la calculatrice à recevoir un nouveau nombre.

10. Rôle de Operateur.appliquer(::)

let result = op.appliquer(prev, current)

C’est l’énumération Operateur qui fait le vrai travail mathématique :

  • .additiona + b
  • .soustractiona - b
  • .multiplicationa * b
  • .division → gestion deb == 0 + a / b La vue, elle, ne fait que :
  1. lire les nombres (currentValue, previousValue),
  2. appeler appliquer,
  3. afficher le résultat avec format.
import SwiftUI
enum Operateur: String {
case addition = "+"
case soustraction = "-"
case multiplication = "×"
case division = "÷"
func appliquer(_ a: Double, _ b: Double) -> Double {
switch self {
case .addition: return a + b
case .soustraction: return a - b
case .multiplication: return a * b
case .division: return b == 0 ? 0 : a / b
}
}
}
struct RetroCalculatorView: View {
@State private var display = "0"
@State private var previousValue: Double? = nil
@State private var currentOperator: Operateur? = nil
@State private var isEnteringNewNumber = true
var body: some View {
VStack(spacing: 20) {
retroScreen
VStack(spacing: 12) {
HStack(spacing: 12) {
retroButton("C") { tapClear() }
retroButton("+/-") { toggleSign() }
retroButton("%") { applyPercent() }
retroButton("÷") { tapOperator(.division) }
}
HStack(spacing: 12) {
retroButton("7") { tapDigit("7") }
retroButton("8") { tapDigit("8") }
retroButton("9") { tapDigit("9") }
retroButton("×") { tapOperator(.multiplication) }
}
HStack(spacing: 12) {
retroButton("4") { tapDigit("4") }
retroButton("5") { tapDigit("5") }
retroButton("6") { tapDigit("6") }
retroButton("-") { tapOperator(.soustraction) }
}
HStack(spacing: 12) {
retroButton("1") { tapDigit("1") }
retroButton("2") { tapDigit("2") }
retroButton("3") { tapDigit("3") }
retroButton("+") { tapOperator(.addition) }
}
HStack(spacing: 12) {
retroButton("0") { tapDigit("0") }
retroButton(".") { tapDecimal() }
retroButton("=") { tapEquals() }
}
}
.padding(.horizontal)
Spacer()
}
.padding(.top, 30)
}
// MARK: - Retro UI Components
private func retroButton(_ title: String, action: @escaping () -> Void) -> some View {
Button(action: action) {
Text(title)
.font(.system(size: 28, weight: .bold, design: .monospaced))
.foregroundStyle(.black)
.frame(maxWidth: .infinity, minHeight: 60)
.padding(.vertical, 4)
.background(Color(red: 0.85, green: 0.85, blue: 0.85))
.overlay(Rectangle().stroke(Color.black, lineWidth: 1))
.shadow(color: .black.opacity(0.4), radius: 3, x: 2, y: 2)
}
.buttonStyle(.plain)
}
private var retroScreen: some View {
Text(display)
.font(.system(size: 48, weight: .medium, design: .monospaced))
.frame(maxWidth: .infinity, minHeight: 80)
.padding()
.background(Color(red: 0.7, green: 0.9, blue: 0.7))
.overlay(RoundedRectangle(cornerRadius: 8).stroke(Color.black, lineWidth: 2))
.padding(.horizontal)
}
// MARK: - Logic (identique à la version précédente)
private func tapDigit(_ digit: String) {
if isEnteringNewNumber || display == "0" {
display = digit
} else {
display.append(digit)
}
isEnteringNewNumber = false
}
private func tapDecimal() {
if isEnteringNewNumber {
display = "0."
isEnteringNewNumber = false
} else if !display.contains(".") {
display.append(".")
}
}
private func tapOperator(_ op: Operateur) {
let current = currentValue
if let prev = previousValue, let currentOp = currentOperator, !isEnteringNewNumber {
let result = currentOp.appliquer(prev, current)
previousValue = result
display = format(result)
} else {
previousValue = current
}
currentOperator = op
isEnteringNewNumber = true
}
private func tapEquals() {
let current = currentValue
if let prev = previousValue, let op = currentOperator {
let result = op.appliquer(prev, current)
display = format(result)
previousValue = nil
currentOperator = nil
isEnteringNewNumber = true
}
}
private func tapClear() {
display = "0"
previousValue = nil
currentOperator = nil
isEnteringNewNumber = true
}
private func toggleSign() {
display = format(-currentValue)
isEnteringNewNumber = true
}
private func applyPercent() {
display = format(currentValue / 100)
isEnteringNewNumber = true
}
private var currentValue: Double { Double(display) ?? 0 }
private func format(_ value: Double) -> String {
if value.rounded() == value {
return String(Int(value))
}
return String(value)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment