Me suis-je pas trop pris la tête ? (correction de l'application dizainier du MOOC de la Sorbonne)

Salut à tous et à toutes voila je suis les cours de Fabrice Kordon (que j'ai cru comprendre Draken connais bien) j'ai réalisé
une des première application le dizainier j'ai l'impression de mettre un peut compliquer la vie alors je vous propose de me dire ce que vous penser de ce code stp =\ je sais pas trop si ce genre de requête a le mérite d'exister sur ce forum si c'est pas le cas alors je ne le referais pas =)

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var stepper: UIStepper!
    @IBOutlet weak var slider: UISlider!
    @IBOutlet weak var chiffreUnite: UISegmentedControl!
    @IBOutlet weak var chiffreDizaine: UISegmentedControl!
    @IBOutlet weak var numberLabel: UILabel!
    var debutChiffre = 5
    var finChiffre = 0
    var geekMode = false
    override func viewDidLoad() {
        super.viewDidLoad()

        // Do any additional setup after loading the view, typically from a nib.
    }
    @IBAction func actionSlider(_ sender: UISlider) {
        let chiffre1 = (Int(sender.value)) / 10
        let chiffre2 = (Int(sender.value)) % 10
        creerText(chiffre1, chiffre2)
    }
    @IBAction func dizaineAction(_ sender: UISegmentedControl) {
        finChiffre = sender.selectedSegmentIndex
        creerText(debutChiffre, finChiffre)
    }
    @IBAction func centaineAction(_ sender: UISegmentedControl) {
        debutChiffre = sender.selectedSegmentIndex
        creerText(debutChiffre, finChiffre)

    }

    func creerText(_ chiffre1: Int, _ chiffre2: Int){
        numberLabel.text = "\((chiffre1))" + "\(chiffre2)"
        stepper.value = Double(numberLabel.text!)!
        chiffreDizaine.selectedSegmentIndex = (Int(numberLabel.text!)! / 10)
        chiffreUnite.selectedSegmentIndex = (Int(numberLabel.text!)! % 10)
        slider.value = Float(numberLabel.text!)!
        color42()
        guard geekMode else {return}
        let chiffre = String(Int(numberLabel.text!)!, radix: 16)
        numberLabel.text = "\(chiffre)"
        color42()

    }
    @IBAction func geekMode(_ sender: UISwitch) {
        guard sender.isOn else {
            geekMode = false
            return
        }
        geekMode = true
    }

    @IBAction func step(_ sender: UIStepper) {
        let chiffre1 = (Int(sender.value)) / 10
        let chiffre2 = (Int(sender.value)) % 10
        creerText(chiffre1, chiffre2)
    }

    @IBAction func raz(_ sender: UIButton) {
        creerText(5, 0)
    }
    func color42(){
        if Int(numberLabel.text!) == 42 {
            numberLabel.textColor = #colorLiteral(red: 0.4392156899, green: 0.01176470611, blue: 0.1921568662, alpha: 1)
        }else {
            numberLabel.textColor = UIColor.black
        }
    }
}

Réponses

  • DrakenDraken Membre

    Euh .. tu ne peux pas plutôt donner un lien vers ton projet ? C’est pas facile à lire, là. Et on ne vois pas l’interface de l’application. Pour le moment, je vois surtout beaucoup trop de « !" un peu partout dans ton code. Les « !" c’est le Mal absolu ..

    Sinon par convention on met plutôt les IBAction en bas de code.

    Je vais fouiller dans mes archives pour retrouver ma version de l’exercice.

  • nico63nico63 Membre

    Salut, merci de tas réponse j'ai publier mon code sur GitHub: https://github.com/nico6308/dizainier-nicopicks

    et le liens de l'exercice =) : https://youtube.com/watch?time_continue=119&v=8HSCYm5apv0

  • DrakenDraken Membre

    Bon, il y a beaucoup à dire. Je n’ai pas le temps là pour tout t'expliquer, je dois sortir.

    Une chose saute au yeux : tu n’as pas compris le principe des contraintes. Tu as placé tes contrôles graphiques sur l’écran, sans te soucier de leurs donner des règles d’adaptation pour les différentes tailles de device.

    Ton interface fonctionne sur un iPhone 8+, parce que c’est le mode de base de l’éditeur de Storyboard. Par contre, c’est la catastrophe dans les autres résolutions (iPhone 4S, iPhone 5, etc..), les contrôles sortent de l’écran. Ne parlons même pas du changement d’orientation avec le mode paysage.

    Je te fais un topo sur la manière de régler ça en revenant.

  • nico63nico63 Membre
    20 janv. modifié #5

    Oui alors je m'intéressais surtout au code et a la fonctionnalité de celui ci en priorité j'ai compris comment fonctionne auto l'atout je n'est pas pris le temps de le mettre en application par contre comment déballer les optionnel sans les "!" avec les guard ?

    [EDIT]: Je viens de mettre des contrainte (uniquement pour le mode paysage)

  • DrakenDraken Membre
    20 janv. modifié #6

    Je viens de mettre des contrainte (uniquement pour le mode paysage)

    Il y a une astuce pour placer correctement les contraintes : configurer le Storyboard en mode iPhone 4S, la plus petite résolution possible. C’est toujours plus facile d’agrandir une présentation pour les autres modes graphiques, que de la rétrécir.

    par contre comment déballer les optionnel sans les "!" avec les guard ?

            // Exemple de variable optionnelle
            let monTexte:String? = "Hello"
    
            // Déballage de la String? en toute sécurité avec guard
            guard let texte = monTexte else { return }
            // On peut utiliser texte normalement à partir d'ici
            print ("Le texte : ", texte)
    

    Ou employer if let

            if let texte = monTexte {
                // Attention, texte n'existe qu'entre les deux accolades
                print ("Le texte : ", texte)
            }
    

    Ou encore ajouter un else après le test pour faire un traitement particulier si la variable est vide

      // Exemple de variable optionnelle
      let monTexte:String? = « Hello"
    
     if let texte = monTexte {
            // Attention, texte n'existe qu'entre les deux accolades
            print ("Le texte : ", texte)
     } else {
             // Traitement si l'optionnelle est vide
             print ("Erreur : la variable monTexte est VIDE !")
     }
    
  • nico63nico63 Membre
    21 janv. modifié #7

    Ok, merci de ton aide =) j'ai essayer de conventionner un peut mon code il est a jour pouvez-vous me dire si c'est correct ?
    j'ai utilisé

    guard let chiffreString: String = numberLabel.text else {return} 
    guard let chiffreInt = Int(chiffreString) else {return}
    

    vaut-il mieux faire comme ça ou alors:

    guard let chiffreInt = Int(numberLabel.text!) else {return} ?

  • DrakenDraken Membre
    21 janv. modifié #8

    ou alors:

    guard let chiffreInt = Int(numberLabel.text!) else {return} ?

    NON ! CA C’EST MAL !

    Ce code est une cause potentielle de plantage de l’application.
    Imagine que pour une raison x ou y, le champ .text de numberLabel soit vide. Appliquer l’opérateur « ! » sur une variable optionnelle vide, c’est le plantage immédiat.

    C’est pourquoi il faut toujours passer par un if let ou un guard pour accéder au contenu d’une variable optionnelle.


    Guard peut exécuter plusieurs tests en même temps, en utilisant une virgule pour chaîner les opérations.

    guard let chiffreString: String = numberLabel.text else {return} 
    guard let chiffreInt = Int(chiffreString) else {return}
    

    Peut être remplacé par :

    guard let chiffreString = numberLabel.text,
          let chiffreInt = Int(chiffreString)
     else {return}
    

    Le fonctionnement est identique, en plus lisible. Et surtout, le code peut utiliser directement les nouvelles variables non-optionnelles. Par exemple : la seconde ligne utilise la variable non-optionnelle chiffreString que j’ai créé plus haut. Si le guard échoue à la première ligne, il ne vas pas exécuter les autres lignes, ce qui élimine le risque de plantage.

  • nico63nico63 Membre

    Ok merci de l'explication à retenir les "!" c'est bannis =) merci Draken

  • Joanna CarterJoanna Carter Membre, Modérateur
    21 janv. modifié #10

    Juste pour m'amuser, j'ai fait le projet selon le façon d'une dev pro (moi) :D

    Marques bien que les données (le chiffre) maintient la valeur ; toute les actions ne modifie que la valeur ; et après la valeur a été modifié, tous les contrôles sont mise à jour de la valeur.

    Malgré le 'truc" de M. Cordon, n'utilises jamais les contrôles pour garder les données.

    class ViewController: UIViewController
    {
      @IBOutlet weak var stepper: UIStepper!
    
      @IBOutlet weak var slider: UISlider!
    
      @IBOutlet weak var segmentedControlUnite: UISegmentedControl!
    
      @IBOutlet weak var segmentedControlDizaine: UISegmentedControl!
    
      @IBOutlet weak var numberLabel: UILabel!
    
      var chiffre = 0
      {
        didSet
        {
          miseAJour()
        }
      }
    
      var geekMode = false
      {
        didSet
        {
          miseAJourNumberLabel()
        }
      }
    
      override func viewDidLoad()
      {
        super.viewDidLoad()
    
        miseAJour()
      }
    
      func miseAJourNumberLabel()
      {
        numberLabel.text = geekMode ? String.init(format: "%x", chiffre) : "\(chiffre)"
    
        numberLabel.textColor = chiffre == 42 ? .red : .black
      }
    
      func miseAJour()
      {
        stepper.value = Double(chiffre)
    
        let chiffreDizaine = chiffre / 10
    
        segmentedControlDizaine.selectedSegmentIndex = chiffreDizaine
    
        let chiffreUnite = chiffre % 10
    
        segmentedControlUnite.selectedSegmentIndex = chiffreUnite
    
        slider.value = Float(chiffre)
    
        miseAJourNumberLabel()
      }
    
      @IBAction func step(_ sender: UIStepper)
      {
        chiffre = Int(sender.value)
      }
    
      @IBAction func dizaineAction(_ sender: UISegmentedControl)
      {
        chiffre = sender.selectedSegmentIndex * 10 + chiffre % 10
      }
    
      @IBAction func uniteAction(_ sender: UISegmentedControl)
      {
        chiffre = chiffre / 10 * 10 + sender.selectedSegmentIndex
      }
    
      @IBAction func actionSlider(_ sender: UISlider)
      {
        chiffre = Int(sender.value)
      }
    
      @IBAction func geekMode(_ sender: UISwitch)
      {
        geekMode = sender.isOn
      }
    
      @IBAction func raz(_ sender: UIButton)
      {
        chiffre = 0
      }
    }
    
  • nico63nico63 Membre
    21 janv. modifié #11
    Merci Joanna pour cette correction qui me paraît clair juste une question peut-tu m’expliquer cette syntaxe

    numberLabel.text = geekMode ? String.init(format: "%x", chiffre) : "\(chiffre)
  • Joanna CarterJoanna Carter Membre, Modérateur
    21 janv. modifié #12

    C'est l'équivalent de :

      if geekMode
      {
        numberLabel.text = String.init(format: "%x", chiffre)
      }
      else
      {
        numberLabel.text = "\(chiffre)"
      }
    
  • Ok dac cool merci pour ce piti cours =D
  • LarmeLarme Membre
    22 janv. modifié #14

    C'est un if ternaire si tu veux en savoir plus...
    En bref:
    if (a) { do(A) } else { do(B) } se transforme en a ? do(A) : do(B), ie: est-ceQueCondition?alorsCasConditionOui:alorsCasConditionNon

  • DrakenDraken Membre
    23 janv. modifié #15

    Le dizainier que j’ai réalisé pour le MOOC était plus compliqué que celui de Nounours. Tu l’as peut-être téléchargé à partir du forum de Kordon ? Je me suis amusé à y ajouter des fonctions « geek » pour traduire les nombres en latin et Dotrakis (un des peuples de Game Of Thrones). C’est amusant, mais pas très pédagogique ..


    L’un des but de l'exercice du Dizainier est de comprendre les bases du paradigme MVC, fondamental en programmation iOS (et macOS).

    L’idée est qu’une application est composée d’un Modèle (un ensemble de données) et d’une Vue (l’interface graphique). Les deux sont totalement indépendants et n’ont aucune connexion. C’est le Contrôleur qui se charge de la communication entre les deux.

    MVC = Modèle, Vue, Contrôleur

    La Vue n’est qu’une représentation graphique du Modèle. La logique de fonctionnement est qu’à chaque événement de l’interface, la Vue prévienne le Contrôleur, qui en informe le Modèle, puis modifie le contenu de la Vue.


    Le Modèle de Nounours est simple, juste deux variables définissant l’état de l’application :

    • chiffre contenant la valeur du nombre courant.
    • geekMode indiquant la nature de l'affichage

    A chaque modification d’une variable du Modèle, la Vue (l’interface) doit être redessiner pour visualiser les nouvelles données.

    Pour ce faire, nounours a utilisé l’instruction Didset permettant d’associer du code à une variable. A chaque modification du contenu de la variable, le code est exécuté.

          var chiffre = 0
          {
            didSet
            {
              miseAJour()
            }
          }
    

    La variable est initialisée à 0, mais pour respecter le cahier de charge vidéo du MOOC, elle devrais être à 5. Il faut toujours faire attention aux conditions d’initialisation d’une application.

          func miseAJour()
          {
            stepper.value = Double(chiffre)
    
            let chiffreDizaine = chiffre / 10
    
            segmentedControlDizaine.selectedSegmentIndex = chiffreDizaine
    
            let chiffreUnite = chiffre % 10
    
            segmentedControlUnite.selectedSegmentIndex = chiffreUnite
    
            slider.value = Float(chiffre)
    
            miseAJourNumberLabel()
          }
    

    La fonction miseAJour() redessine l’intégralité de l’interface en partant des variables du Modèle. Elle centralise tout le code de dessin, ce qui évite d’avoir des fragments de code ici et là. En cas de de modification des fonctionnalités (ajout d’une autre contrôle par exemple), on sait que son affichage doit se faire ici. La maintenance du code (la possibilité de faire évoluer l’application, par la suite) est une chose importante, trop souvent ignorée par les novices.

    Il est important de noter que les informations pour dessiner l’interface viennent TOUTES du Modèle. On ne doit jamais lire le contenu d’un contrôle graphique pour modifier l’état de l’interface !


    Quand l’état d’un contrôle graphique est modifié, il prévient le Contrôleur avec une IBAction.

          @IBAction func dizaineAction(_ sender: UISegmentedControl)
          {
            chiffre = sender.selectedSegmentIndex * 10 + chiffre % 10
          }
    

    La fonction réagit à l’événement en modifiant la variable du Modèle. Tu peux remarquer qu’il n’y a pas d’histoire d’opérateur « ! » ici, ni d’optionnelle. Le sender (l’objet ayant déclenché l’événement) existe forcément. Et l’information qu’il contient aussi.

    C’est simple à comprendre et à coder. Dés que la variable chiffre est modifiée, son Didset actualise l’interface en appelant la fonction miseAJour(). C’est le cycle de base d’une application MVC :

    • la Vue averti le Contrôleur d’un événement.
    • le Controleur modifie le contenu du Modéle en fonction de l’évenement
    • Le Controleur actualise la Vue à partir des données du Modèle

    Petite remarque technique, nico63 : tu as donné des noms simples aux IBOutlet de ton application.

        @IBOutlet weak var chiffreUnite: UISegmentedControl!
    
        @IBOutlet weak var chiffreDizaine: UISegmentedControl
    

    Cela donne un code peu lisible, avec un risque de confusion avec d’autres variables. Par convention on donne toujours un nom reconnaissable aux outlets. Nounours inclut toujours le type d’un outlet dans son nom :

          @IBOutlet weak var segmentedControlUnite: UISegmentedControl!
    
          @IBOutlet weak var segmentedControlDizaine: UISegmentedControl!
    

    C’est la manière la plus courante de procéder. Je fais la même chose. D’autres développeurs préfèrent commencer le nom des outlets par ui (pour User Interface).

      @IBOutlet weak var ui_Unite: UISegmentedControl!
    
      @IBOutlet weak var ui_Dizaine: UISegmentedControl!
    

    Les deux styles se valent. Ce qui est important c’est que l’on puisse distinguer les outlets des autres variables, d’un simple regard dans le code.

  • Merci beau Draken pour le commentaire et l’explication du code
  • A ton service. N’hésite pas à poser d’autres questions.

Connectez-vous ou Inscrivez-vous pour répondre.