UPGRADE YOUR SKILLS: Learn advanced Swift and SwiftUI on Hacking with Swift+! >>

Des outlets aux actions : IBAction et interpolation de chaînes de caractères

J'ai dit que nous allions revenir à Interface Builder, et bien le moment est venu : nous allons connecter l'action "toucher" de nos UIButtons à du code. Donc, sélectionnez Main.storyboard, puis basculez dans le mode assistant editor pour pouvoir voir le code à côté de la mise en forme de l'interface.

Attention : veuillez lire le texte suivant très attentivement. Dans ma hâte, je me trompe tout le temps et je ne veux pas que vous soyez embêtés avec cela !

Sélectionnez le premier bouton, puis maintenez la touche Ctrl enfoncée et faites-le glisser dans votre code immédiatement après la fin de la méthode askQuestion(). Si vous le faites correctement, vous devriez voir une info-bulle affichant "Insert Outlet, Action, or Outlet Collection". C'est la même fenêtre que celle que vous voyez lorsque vous créez des outlets, donc faites bien attention et ne choisissez pas outlet.

La création d'une action dans l'éditeur de Xcode est très similaire à la création d'un outlet.

C'est bon : là où est écrit "Connection: Outlet" en haut de la fenêtre, je veux que vous changiez cela en "Action". Si vous choisissez Outlet ici (ce que je fais trop souvent parce que je suis pressé), cela va vous poser problème !

Lorsque vous choisissez Action plutôt que Outlet, la fenêtre contextuelle change un peu. On vous demandera quand même un nom, mais vous voyez maintenant un champ Event et le champ Type est passé de UIButton à Any. Remettez le champ Type à UIButton puis entrezbuttonTapped pour le nom (champ Name), puis cliquez sur Connect.

Voici ce que Xcode écrira pour vous :

@IBAction func buttonTapped(_ sender: UIButton) {
}

…et une fois de plus, remarquez le cercle gris entouré d'un anneau à gauche, ce qui signifie qu'il existe une connexion dans Interface Builder.

Avant de regarder ce que cela fait, je veux que vous établissiez deux autres connexions. Cette fois, c'est un peu différent, car nous connectons les deux autres boutons à la même méthode buttonTapped(). Pour ce faire, sélectionnez chacun des deux boutons restants, puis faites glisser sur la méthode buttonTapped() qui vient d'être créée, en maintenant la touche Ctrl enfoncée. Toute la méthode devient bleue, indiquant qu'elle est connectée. Si la méthode clignote après que vous ayez relaché le bouton de la souris et la touche Ctrl, cela signifie que la connexion a été établie.

Alors, qu'est-ce que nous avons ? Et bien nous avons une seule méthode appelée buttonTapped(), qui est connectée aux trois UIButtons. L’événement utilisé pour la connexion est appelé TouchUpInside, qui est le moyen utilisé par iOS pour dire "l’utilisateur a appuyé sur ce bouton, puis a relâché son doigt alors qu’il était toujours dessus" - c’est-à-dire que le bouton a été touché.

Encore une fois, Xcode a inséré un attribut au début de cette ligne pour qu'il sache qu'elle a un rapport avec Interface Builder, et cette fois, il s'agit de @IBAction. @IBAction est similaire à @IBOutlet, mais il n'est pas utilisé pour la même chose : @IBOutlet est un moyen de connecter le code à des objets du storyboard et @IBAction est un moyen de déclencher du code en fonction d'événements liés à des objets du storyboard.

Cette méthode prend un paramètre, appelé sender. Il est de type UIButton car nous savons que c'est un boutton qui va appeler la méthode. Et ceci est important : les trois boutons appellent la même méthode. Il est donc important de savoir quel bouton a été touché pour pouvoir déterminer si la réponse est correcte.

Mais comment savoir si l'utilisateur a appuyé sur le bon bouton ? À l'heure actuelle, tous les boutons ont le même aspect, mais en coulisse, toutes les vues ont un numéro d'identification spécial que nous pouvons définir, appelé Tag. Ca peut être n'importe quel nombre, alors nous allons donner à nos boutons les nombres 0, 1 et 2. Ce n'est pas un hasard : notre code est déjà configuré pour mettre les drapeaux 0, 1 et 2 dans ces boutons, si nous leur attribuons les mêmes balises, nous savons exactement sur quel drapeau l'utilisateur a appuyé.

Sélectionnez le deuxième drapeau (pas le premier !), puis recherchez dans l'inspecteur des propriétés (attributes inspector : Alt + Cmd + 4) la zone de saisie marquée Tag. Vous devrez peut-être faire défiler l'écran vers le haut, car les UIButtons ont beaucoup de propriétés avec lesquelles nous pouvons travailler ! Une fois que vous l'avez trouvée (environ aux deux tiers de la hauteur, juste au-dessus des propriétés color et alpha), assurez-vous qu'elle soit définie sur 1.

Attribuer une valeur à la propriété Tag d'une vue dans Interface Builder est un moyen rapide et facile de les distinguer les unes par rapport aux autres.

Maintenant, choisissez le troisième drapeau et définissez sa propriété tag sur 2. Nous n’avons pas besoin de changer celle du premier drapeau car 0 est la valeur par défaut.

Nous en avons terminé avec Interface Builder pour l'instant. Revenez à l'éditeur standard et sélectionnez ViewController.swift. Il est temps de terminer en remplissant le contenu de la méthode buttonTapped().

Cette méthode doit faire trois choses :

  1. Vérifier si la réponse est correcte.
  2. Ajuster le score du joueur vers le haut ou le bas.
  3. Afficher un message pour lui dire quel est son nouveau score.

La première tâche est assez simple, car chaque bouton a un tag correspondant à sa position dans le tableau et nous avons stocké la position de la bonne réponse dans la variable correctAnswer. Donc, la réponse est correcte si sender.tag est égal à correctAnswer.

La deuxième tâche est également simple, car vous avez déjà rencontré l’opérateur += qui ajoute une valeur. Nous allons utiliser cela et son équivalent, -=, pour ajouter ou soustraire le score selon les besoins.

La troisième tâche est plus compliquée, nous allons l'aborder dans une minute. Autant dire qu'elle introduit un nouveau type de données qui affichera une fenêtre contenant un message à l'utilisateur avec un titre et son score actuel.

Ecrivez ce code dans la méthode buttonTapped() :

var title: String

if sender.tag == correctAnswer {
    title = "Correct"
    score += 1
} else {
    title = "Wrong"
    score -= 1
}

Il y a deux nouvelles choses ici :

  1. Nous utilisons l'opérateur ==. C'est l'opérateur d'égalité qui vérifie si la valeur de gauche correspond à la valeur de droite. Le résultat sera vrai si la propriété tag du bouton tapé est égale à la variable correctAnswer que nous avons enregistrée dans askQuestion(), sinon il sera faux.
  2. Nous avons une déclaration else. Lorsque vous écrivez une condition if, vous ouvrez une accolade, écrivez du code, puis fermez cette accolade, et ce code sera exécuté si la condition est vraie (true). Mais vous pouvez également donner à Swift un code qui sera exécuté si la condition évaluée est fausse (false), ce qui constitue le bloc "else". Ici, nous définissons un titre si la réponse est correcte et un titre différent si elle est fausse.

Passons maintenant au problème : nous allons utiliser un nouveau type de données appelé UIAlertController(). Celui-ci est utilisé pour afficher une alerte avec des options pour l'utilisateur. Pour que cela fonctionne, vous devez apprendre deux nouvelles choses, alors abordons-les avant de les assembler.

La première chose à apprendre est appelée interpolation de chaîne de caractères. Il s'agit d'une fonctionnalité de Swift qui vous permet de placer des variables et des constantes directement dans des chaînes de caractères et de les remplacer par leur valeur actuelle lors de l'exécution du code. Pour le moment, nous avons une variable de type entier (Interger) appelée score, donc nous pouvons l'insérer dans une chaîne de caractères comme ceci :

let mytext = "Your score is \(score)."

Si le score est égal à 10, mytext se lira "Votre score est 10". Comme vous pouvez le constater, vous écrivez simplement \(, puis le nom de votre variable, puis ) et vous avez terminé. Swift peut effectuer toutes sortes d'interpolations de chaînes de caractères, mais nous en resterons là pour l'instant.

La deuxième chose à apprendre s'appelle closure. Il s'agit d'un type spécial de bloc de code qui peut être utilisé comme une variable - Swift prend littéralement une copie du bloc de code afin qu'il puisse être appelé ultérieurement. Swift copie également tout ce qui est référencé dans le code, vous devez donc faire attention à la façon dont vous les utilisez. Nous utiliserons beaucoup les closures plus tard, mais pour le moment, nous allons prendre deux raccourcis.

Maintenant que l'apprentissage initial est effectué, regardons donc comment ça se passe avec du code. Entrez ceci juste avant la fin de la méthode buttonTapped() :

let ac = UIAlertController(title: title, message: "Your score is \(score).", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "Continue", style: .default, handler: askQuestion))
present(ac, animated: true)

Ce code produit une erreur pour le moment, mais il est quand même correct.

La variable title a été définie dans notre instruction if pour être "correcte" ou "incorrecte", et vous avez déjà entendu parler de l'interpolation de chaîne de caractères. Le premier élément nouveau est le paramètre .alert utilisé pour preferredStyle. Si vous vous souvenez d'avoir utilisé .normal pour la méthode setImage() des UIButtons, vous devriez reconnaître qu'il s'agit d'une enum ou d'une énumération.

Dans le cas de UIAlertController(), il existe deux types de style : .alert, qui ouvre une boîte contenant un message au centre de l'écran, et .actionSheet, qui fait glisser les options vers le haut. Ils sont similaires, mais Apple vous recommande d'utiliser .alert pour informer les utilisateurs d'un changement de situation et .actionSheet pour leur demander de choisir parmi un ensemble d'options.

La deuxième ligne utilise le type de données UIAlertAction pour ajouter à l'alerte un bouton indiquant "Continue" en lui donnant le style "default". Il existe trois styles possibles : .default, .cancel et .destructive : leur apparence dépend d’iOS, mais il est important de les utiliser de manière appropriée, car elles fournissent des indications subtiles dans l’interface utilisateur.

La surprise finale est à la fin de cette ligne : handler: askQuestion. Le paramètre handler recherche une closure, c'est-à-dire un code qu'il peut exécuter lorsque le bouton est touché. Vous pouvez écrire du code personnalisé si vous le souhaitez, mais dans notre cas, nous voulons que le jeu continue lorsque le bouton est tapé. Nous passons donc askQuestion afin qu'iOS appelle notre méthode askQuestion().

Attention : Nous devons utiliser askQuestion et non askQuestion(). Si vous utilisez le premier, cela signifie "voici le nom de la méthode à exécuter", mais si vous utilisez le dernier, cela signifie "exécute maintenant la méthode askQuestion(), et le nom de la méthode à exécuter sera indiqué."

Il y a beaucoup de bonnes raisons d'utiliser des closures, mais dans l'exemple ci-dessous, le simple passage de askQuestion est un raccourci génial - bien qu'il provoque une erreur que nous devrons corriger dans un instant.

La dernière ligne appelle present(), qui prend deux paramètres : un contrôleur de vue à présenter et une animation éventuelle lors de cette présentation. Il contient un troisième paramètre facultatif, qui est une autre closure à exécuter à la fin de l’animation de la présentation, mais nous n’en avons pas besoin ici. Nous envoyons notre UIAlertController pour le premier paramètre et true pour le second car c'est toujours agréable d'avoir une animation.

Il y a un problème avec notre code et Xcode vous dit de quoi il s'agit : “Cannot convert value of type ‘() -> ()’ to expected argument type ‘((UIAlertAction) -> Void)?’". (Impossible de convertir la valeur de type ‘() -> ()’ en type d’argument attendu ‘((UIAlertAction) -> Void)?'".

C’est un bon exemple des terribles messages d’erreur de Swift, et je crains que vous ne deviez vous y habituer. Cela veut dire que l'utilisation d'une méthode pour cette closure est acceptable, mais Swift veut que la méthode accepte un paramètre UIAlertAction indiquant quel UIAlertAction a été exploité.

Pour résoudre ce problème, nous devons modifier la définition de la méthode askQuestion(). Alors, modifiez askQuestion() :

func askQuestion() {

…comme ceci :

func askQuestion(action: UIAlertAction!) {

Cela corrigera l’erreur UIAlertAction mais introduira cependant un autre problème : lors de la première utilisation de l'application, nous appelons askQuestion() dans viewDidLoad() et nous ne lui transmettons pas de paramètre. Il y a deux façons de résoudre ce problème :

  1. Lors de l'utilisation de askQuestion() dans viewDidLoad(), nous pourrions lui envoyer le paramètre nil pour indiquer "il n'y a pas de UIAlertAction pour cela."
  2. Nous pourrions redéfinir askQuestion() pour que le paramètre action soit nil par défaut, ce qui signifie que s'il n'est pas spécifié, il devient automatiquement nil.

Il n'y a pas de bonne ou de mauvaise réponse ici, alors je vais vous montrer les deux et vous pourrez choisir. Si vous voulez utiliser la première option, remplacez l'appel askQuestion() dans viewDidLoad() par ceci :

askQuestion(action: nil)

Et si vous voulez utiliser la deuxième option, modifiez la définition de la méthode askQuestion() comme ceci :

func askQuestion(action: UIAlertAction! = nil) {

Lancez maintenant votre programme dans le simulateur, parce que c'est fini !

Taper sur les boutons fonctionne maintenant - vous voyez si vous avez raison ou tort.

BUILD THE ULTIMATE PORTFOLIO APP Most Swift tutorials help you solve one specific problem, but in my Ultimate Portfolio App series I show you how to get all the best practices into a single app: architecture, testing, performance, accessibility, localization, project organization, and so much more, all while building a SwiftUI app that works on iOS, macOS and watchOS.

Get it on Hacking with Swift+

Sponsor Hacking with Swift and reach the world's largest Swift community!

BUY OUR BOOKS
Buy Pro Swift Buy Pro SwiftUI Buy Swift Design Patterns Buy Testing Swift Buy Hacking with iOS Buy Swift Coding Challenges Buy Swift on Sundays Volume One Buy Server-Side Swift Buy Advanced iOS Volume One Buy Advanced iOS Volume Two Buy Advanced iOS Volume Three Buy Hacking with watchOS Buy Hacking with tvOS Buy Hacking with macOS Buy Dive Into SpriteKit Buy Swift in Sixty Seconds Buy Objective-C for Swift Developers Buy Beyond Code

Was this page useful? Let us know!

 
Unknown user

You are not logged in

Log in or create account
 

Link copied to your pasteboard.