Et nous voilà dans la partie sur le contrôleur ! Cette partie est la plus excitante du cours car nous allons tout connecter pour faire enfin fonctionner notre application ! La logique est prête dans le modèle, la vue est toute belle. Il ne nous reste plus qu'à faire les branchements avec le contrôleur !
Pour rappel, le rôle du contrôleur est de faire le lien entre le modèle et la vue. Le contrôleur va récupérer les données du modèle et les afficher dans la vue.
Dans ce chapitre, nous allons commencer par connecter le contrôleur et la vue.
Le fichier ViewController.swift
Concrètement le contrôleur, c'est le fichier ViewController.swift
qui a été créé automatiquement par Xcode au début du projet.
Dans ce fichier, nous avons une classe ViewController
qui hérite de UIViewController
. Cette classe contient déjà deux méthodes :
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
La première méthode est appelée lorsque le contrôleur a fini d'être chargé donc dans notre cas au lancement de l'application. Nous allons en avoir besoin dans le prochain chapitre pour faire quelques initialisations.
La deuxième méthode est appelée lorsque le contrôleur doit retenir trop d'informations et qu'il n'a plus de place en mémoire pour les stocker. Dans ce cas, il faut libérer de la place. Nous ne rencontrerons pas ce problème donc vous pouvez supprimer la méthode.
Les outlets
Pour connecter le contrôleur et la vue, nous allons utiliser des... connexions ! Il existe deux types de connexion : les outlets et les actions. Nous allons commencer par les outlets.
Les outlets et le MVC
Pour rappel, les outlets sont des connexions entre une vue dans le storyboard et une propriété dans le contrôleur. Nous allons donc créer des propriétés dans notre contrôleur qui représentent nos vues.
Cela veut dire que dans notre modèle MVC, le contrôleur s'adresse à la vue via les outlets.
Créer un outlet
Comment on crée les outlets ?
Vous avez deux options :
soit vous faites comme pour
QuestionView
précédemment en écrivant la propriété et le décorateur@IBOutlet
.soit vous laissez Xcode faire cela pour vous en utilisant le control drag. Et c'est ce que nous allons faire.
Placez-vous en mode assistant avec le storyboard à gauche et le contrôleur à droite. Laissez la touche ctrl enfoncée et glissez depuis le bouton vers le code comme ceci :
Lorsque vous relâchez la souris, une popup vous demande plus d'information sur la connexion que vous souhaitez créer. Il y a plusieurs paramètres :
Connection : le type de connexion que vous souhaitez créer. Ici, on veut bien un Outlet.
Object : Vers où pointe la connexion, c'est bien le
ViewController
.Name : Le nom de la propriété que l'on veut créer. Ici nous allons écrire :
newGameButton
.Type : Le type de la propriété que l'on veut créer. C'est bien
UIButton
.Storage : C'est une notion assez avancée de programmation et vous n'aurez jamais besoin de changer ce paramètre donc ignorons-le pour le moment.
On peut ensuite cliquer sur connect
et le code suivant est généré :
@IBOutlet weak var newGameButton: UIButton!
On a bien le décorateur @IBOutlet
(puis le mot-clé weak
que l'on ignore pour le moment), puis la déclaration d'une propriété newGameButton
de type UIButton
.
Vous pouvez répéter cette opération pour :
l'indicateur d'activité
la vue question
le label score
Si vous choisissez les noms ci-dessous, vous devrez obtenir le code suivant :
@IBOutlet weak var newGameButton: UIButton!
@IBOutlet weak var activityIndicator: UIActivityIndicatorView!
@IBOutlet weak var scoreLabel: UILabel!
@IBOutlet weak var questionView: QuestionView!
Tous nos outlets sont créés ! Nous pouvons désormais manipuler nos vues dans le contrôleur.
Les outlets collections
Pour votre information, il existe un autre type d'outlet que nous n'utiliserons pas ensemble, mais qui est très simple : les outlets collections. Comme leur nom le suggère, ils fonctionnent comme les outlets, mais pour plusieurs vues à la fois.
Par exemple, si on crée une application calculatrice, on pourrait créer un outlet pour chaque bouton de 0 à 9 comme ceci :
@IBOutlet weak var zeroButton: UIButton!
@IBOutlet weak var oneButton: UIButton!
@IBOutlet weak var twoButton: UIButton!
@IBOutlet weak var threeButton: UIButton!
@IBOutlet weak var fourButton: UIButton!
@IBOutlet weak var fiveButton: UIButton!
@IBOutlet weak var sixButton: UIButton!
@IBOutlet weak var sevenButton: UIButton!
@IBOutlet weak var eightButton: UIButton!
@IBOutlet weak var nineButton: UIButton!
C'est un peu redondant et peu pratique à utiliser. On va donc utiliser une outlet collection :
@IBOutlet var numbersButton: [UIButton]!
La propriété créée est un tableau qui contient tous les boutons.
Pour créer une outlet collection, il suffit de choisir ce type de connexion dans la popup après le control drag. Puis pour remplir le tableau, il faut faire un control drag depuis chaque bouton vers la propriété numbersButton
.
Les actions
Nous voulons non seulement contrôler les vues. Mais aussi recevoir des informations de leur part. Par exemple, comment faire pour exécuter du code lorsque le bouton est tapé ?
Les actions et le MVC
Nous devons donc aborder l'épineuse question de la communication de la vue vers le contrôleur. La vue a-t-elle le droit de communiquer avec le contrôleur ?
Et la réponse est oui... mais indirectement. Via les actions. Les actions sont des connexions entre une vue et une méthode du contrôleur associées à un évènement.
Schématiquement le contrôleur va placer une cible sur lui-même. Ainsi la vue va pouvoir lui envoyer un message à chaque fois que le bouton est tapé.
Créer une action
Pour créer une action, on va faire comme pour les outlets. On va effectuer un control drag depuis le bouton vers le code. Mais cette fois-ci, nous allons choisir Action pour le paramètre Connection :
Il y à nouveau plusieurs paramètres ici :
Connection et Object : même chose que pour les outlets.
Name : le nom de la méthode que nous allons créer. Ici je vous propose
didTapNewGameButton
.Type : Nous allons créer une méthode. Cette méthode peut avoir des paramètres comme l'évènement et la vue dont vient l'action, appelée le sender. Si on souhaite avoir le sender en paramètre de la méthode, on peut choisir ici le type du sender.
Event : Une action est associée à un évènement. Cet évènement représente le geste que doit réaliser l'utilisateur pour que l'action ait lieu. Par défaut, pour un bouton, cet évènement est Touch Up Inside. Cela signifie un touché vers le haut à l'intérieur du bouton, c'est-à-dire le moment où le doigt quitte le bouton. Je vous invite à regarder la liste pour voir les autres types d'évènements possibles.
Arguments : Comme expliqué précédemment, la méthode peut avoir des paramètres. Ici on décide si on en a besoin ou non. J'ai choisi None pour aucun paramètre. Pour information, les autres possibilités sont le sender seul ou le sender et l'évènement.
Vous pouvez cliquer sur connect et Xcode génère le code suivant :
@IBAction func didTapNewGameButton() {
}
Notre action est créée. La méthode didTapNewGameButton
va être appelée à chaque fois que l'on clique sur le bouton.
Pour différencier l'action et la logique qu'elle implique, je vous recommande de créer deux méthodes séparées. Nous allons donc rajouter une méthode startNewGame
privée à notre classe :
private func startNewGame() {
}
Cette méthode va pouvoir être appelée n'importe où dans le code et notamment dans la méthode didTapNewGameButton
:
@IBAction func didTapNewGameButton() {
startNewGame()
}
Implémenter startNewGame
Il ne nous reste plus qu'à implémenter notre méthode startNewGame
. Réfléchissons un peu à ce qu'elle fait. Elle lance une nouvelle partie donc :
Elle va afficher une interface de chargement
Elle va lancer le chargement des questions
Lorsque les questions sont chargées, la partie peut débuter.
Nous allons nous concentrer sur les points 2 et 3 dans le prochain chapitre car nous avons besoin du modèle pour ça. Pour le moment, nous allons afficher notre interface de chargement. Voici à quoi elle va ressembler :
Nous allons donc :
Cacher le bouton : cela permet d'empêcher l'utilisateur de lancer un nouveau chargement.
Afficher l'indicateur d'activité : pour notifier l'utilisateur que le chargement est en cours.
Remettre le score à zéro.
Remettre la vue question dans le style
standard
: son style a pu avoir été modifié plus tôt si l'utilisateur était en train de jouer.Afficher "Loading..." dans la vue question.
Cacher le bouton et afficher l'indicateur d'activité
Comme toutes les vues, les classes UIButton
et UIActivityIndicatorView
héritent de UIView
. Elles ont donc accès à la propriété isHidden
que nous avons vue dans la partie précédente. Nous avons donc simplement à écrire :
private func startNewGame() {
activityIndicator.isHidden = false
newGameButton.isHidden = true
}
Ainsi, le bouton est caché et l'indicateur d'activité est visible.
La vue question
Nous avons déjà bien travaillé sur notre vue question. Nous avons laissé deux propriétés publiques style
et title
. Nous avons juste à les utiliser ici.
questionView.title = "Loading..."
questionView.style = .standard
Vous voyez en quoi avoir créé une vue customisée nous rend les choses faciles ici !
Le label score
Il ne nous reste plus qu'à remettre à jour le label score. Vous savez maintenant que pour changer le texte d'un UILabel
, on utilise la propriété text
. C'est donc précisément ce que nous allons faire :
scoreLabel.text = "0 / 10"
Et voilà ! Votre méthode startNewGame
doit ressembler à ceci :
private func startNewGame() {
activityIndicator.isHidden = false
newGameButton.isHidden = true
questionView.title = "Loading..."
questionView.style = .standard
scoreLabel.text = "0 / 10"
}
Nous avons maintenant une belle interface de chargement lorsqu'on clique sur le bouton. Vous pouvez tester cela en lançant le simulateur.
En résumé
Pour contrôler les vues, le contrôleur utilise les outlets qui sont des connexions entre une propriété du contrôleur et une vue.
Pour envoyer un message au contrôleur, les vues peuvent utiliser les actions qui sont des connexions entre une méthode du contrôleur et une vue, associées à un évènement.
Pour créer ces connexions, on utilise le control drag depuis la vue vers le code du contrôleur.