Caissier.Électronique

Atelier animé par l’enseignant

Note:  Réalisé sous Xcode 9B6

Étape 1 – Réalisation de la maquette


Objectif:  Réaliser une mise en page qui fonctionne en mode portrait et en mode paysage.
Voici la maquette finale:

caissier-electronique-02

Ressources
calculer 123
Action:  Réaliser la maquette
Note:  Les champs de saisie sont de type UITextField sauf pour tps et tv qui sont de type UILabel.
Action:  Tester l’application dans le simulateur (orientation portrait et paysage).  Faire les ajustements au besoin.
 

Étape 2 – Définir les liens MVC – @IBOUTLET et @IBAction


Action:  Définir les liens suivants:

    @IBOutlet weak var quantité: UITextField!
    @IBOutlet weak var prix: UITextField!
    @IBOutlet weak var total: UITextField!
    @IBOutlet weak var totalAvecTaxes: UITextField!
    @IBOutlet weak var tps: UILabel!
    @IBOutlet weak var tvq: UILabel!
    @IBAction func calculerTotalAvecTaxes(_ sender: AnyObject) {
    } // func calculerTotalAvecTaxes

Action: Au démarrage, initialiser les champs numériques à ‘nil‘:

    override func viewDidLoad() {
        super.viewDidLoad()
        quantité.text = nil
        prix.text = nil
        total.text = nil
        tps.text = nil
        tvq.text = nil
        totalAvecTaxes.text = nil
    }  // viewDidLoad()

 

Étape 3 – Programmer la délégation


Action: Adhérer au protocole UITextFieldDelegate.

class ViewController: UIViewController, UITextFieldDelegate {

Action: Établir un lien entre un composant et un protocole.
delegate
Action: Expérimenter avec les méthodes du protocole UITextFieldDelegate:
textFieldDelegate

    func textFieldDidBeginEditing(_ textField: UITextField) {
        print("#L'utilisateur est entré dans la zone de texte!")
        textField.textColor = UIColor.red
    }
    func textFieldDidEndEditing(_ textField: UITextField) {
        textField.textColor = UIColor.black
    }
    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        print("#L'utilisateur a appuyé sur la touche retour!")
        return true
    }

 

Étape 4 – Programmer les fonctionnalités de l’application


Révision prof

Tester si un champ est vide

Il est possible de tester si un champ est vide de la façon suivante:

    func textFieldDidEndEditing(_ textField: UITextField) {
        textField.textColor = UIColor.black
        if textField.text!.isEmpty == true {
          afficherAlert("Le champ de saisie ne doit pas être vide!")
        }
    }

Afficher une fenêtre d’alerte

Voici comment afficher une fenêtre d’alerte:

    func afficherAlert(_ message:String) {
        let alert = UIAlertController(title: "Attention!", message: message, preferredStyle: UIAlertControllerStyle.alert)
        alert.addAction(UIAlertAction(title: "Je ferai plus attention!", style: UIAlertActionStyle.default, handler: nil))
        // Patch temporaire pour Xcode 8b5; utilisation de 'DispatchQueue' pour exécuter le self.present()
        DispatchQueue.main.async( execute: { self.present(alert,  animated: true, completion: nil) } )
        //self.present(alert,  animated: true, completion: nil)
    }

 
Dans notre cas, il suffit de tester s’il est possible de le convertir en nombre:

if let _ = Float(textField.text!) {
  // calculer le total ...
} else
{
  afficherAlert("Erreur, Il faut entrer un nombre!")
}

 

Quelques conversions numériques

Le contenu d’un UITextField et d’un UILabel est accessible via la propriété ‘text’, comme dans ‘quantité.text’.
La propriété ‘text’ est de type ‘String’.
Avant de pouvoir utiliser son contenu à des fins arithmétiques il faudra le convertir.
Le façon la plus simple, est d’utiliser le constructeur des types numériques.
Par exemple,

Si la chaine représente un nombre valide pour le type, elle sera alors convertie.  Si non, la méthode retournera ‘nil’.  
Cette méthode retourne une valeur  ‘optionnelle‘.
Pour obtenir le résultat, il faudra le ‘déballer’.

Voici comment faire de façon forcée (Forced Unwrapping):

Int(« 123 »)!

ou bien

Float(total.text!)!

Note:  ‘.text’ est aussi une valeur ‘String’ optionnelle.
Donc, pour calculer la TPS sur le total nous pourrions faire ceci:

let TPS:Float = 0.05
let _tps = Float(total.text!)! * TPS

Note: Si nous tentons d’utiliser le point d’exclamation sur une variable optionnelle qui n’a pas de valeur (nil), nous obtiendrons une erreur d’exécution avec le message d’erreur suivant : Fatal error: Unexpectedly found nil value while unwrapping an Optional value.
Dans l’exemple suivant, nous utiliserons le déballage conditionnel.

Déballage conditionnel (Optional Bindings)

Pour calculer le montant en fonction de la quantité et du prix:

if let _quantité = Float(quantité.text!), let _prix = Float(prix.text!) {
   total.text = String(_quantité * _prix)
   // total.text = NSString(format: "%2.2f", _quantité * _prix) as String
}

Note:  La classe NSString offre plus de possibilités de manipulation de la chaine.

L’expression ‘NSString(format: « %2.2f », _quantité * _prix)’  permet de fixer à 2, le nombre de chiffres après le point (virgule).
Référence pour les instructions de formatage (String Format Specifiers).

Autre chose,

Le calcul sera effectué que si Float(quantité.text!) et Float(prix.text!) ne sont pas vides.
‘if let’ est un déballage conditionnel.
Le bloc ‘vrai’ est exécuté que si l’affectation a réussi.
Voici la forme générale:
if let var = expression {
vrai
} else {
faux
}

Derniers détails

Voici le code final pour nos calculs
Calcul du total, suite à un « enter » dans un champ:

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        if let _ = Float(textField.text!) {
            textField.resignFirstResponder()
            if let _quantité = Float(quantité.text!), let _prix = Float(prix.text!) {
                // total.text = String(_quantité * _prix)
                total.text = NSString(format: "%2.2f", _quantité * _prix) as String
            } // if
        } else
        {
             afficherAlert("Erreur, Il faut entrer un nombre!")
             return false
        }  // if
        return true
    }  // textFieldShouldReturn

 
Calcul du grand total (total + taxes):

    @IBAction func calculerTotalAvecTaxes(_ sender: AnyObject) {
        if total.text?.isEmpty == false {
            let _tps = Float(total.text!)! * TPS
            let _tvq = Float(total.text!)! * TVQ
            let _totalAvecTaxes = Float(total.text!)! + _tps + _tvq
            tps.text = NSString(format: "%2.2f$", _tps) as String
            tvq.text = NSString(format: "%2.2f$", _tvq) as String
            totalAvecTaxes.text = NSString(format: "%2.2f$", _totalAvecTaxes) as String
        } // if
    } // func calculerTotalAvecTaxes

 
Et finalement, calculer le grand total automatiquement si le total change:
Ajouter le code suivant:

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    if let _ = Float(textField.text!) {
        textField.resignFirstResponder()
        if let _quantité = Float(quantité.text!), let _prix = Float(prix.text!) {
            // total.text = String(_quantité * _prix)
            total.text = NSString(format: "%2.2f", _quantité * _prix) as String
            calculerTotalAvecTaxes(self)
        } // if
    } else
    {
         afficherAlert("Erreur, Il faut entrer un nombre!")
        return false
    }  // if
    return true
}  // textFieldShouldReturn

Note:  Xcode propose des classes de mise en forme de nombres, de dates, …
Voici un exemple de code qui affiche un ‘Float’ en format monétaire:

func formatCurrency(_ value: Float) -> String {
     let formatter = NumberFormatter()
     formatter.numberStyle = .currency
     formatter.maximumFractionDigits = 2
     formatter.locale = Locale(identifier: Locale.current.identifier)
     let result = formatter.string(from: value as NSNumber)
     return result!
}

 
Le projet terminé est disponible ici.


Document rédigé par Alain Boudreault le 2016.08.18