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.
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 UIButton
s. 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 UIButton
s 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.
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 :
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 :
==
. 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.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 :
askQuestion()
dans viewDidLoad()
, nous pourrions lui envoyer le paramètre nil
pour indiquer "il n'y a pas de UIAlertAction
pour cela."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 !
SPONSORED Alex is the iOS & Mac developer’s ultimate AI assistant. It integrates with Xcode, offering a best-in-class Swift coding agent. Generate modern SwiftUI from images. Fast-apply suggestions from Claude 3.5 Sonnet, o3-mini, and DeepSeek R1. Autofix Swift 6 errors and warnings. And so much more. Start your 7-day free trial today!
Sponsor Hacking with Swift and reach the world's largest Swift community!
Link copied to your pasteboard.