Notre TableView est bien installée, mais, pour l'instant, elle est complètement vide. Cela ne va pas durer ! Dans ce chapitre, je vais vous présenter le mécanisme qui va nous permettre de la remplir : le delegate pattern !
Présentation du delegate pattern
Commençons par faire un retour sur le MVC :
Avec les outlets, le contrôleur peut donner à la vue les données dont elle a besoin pour s'afficher. Cette méthode fonctionne bien, mais a ses limites. Elle ne marche que pour de petites quantités de données.
Or, comme on l'a vu, une liste peut avoir des tonnes de données ! On ne va pas tout donner à la vue en lui demandant de se débrouiller avec. La vue ne doit détenir que les données qui lui permettent de faire l'affichage, car c'est son seul rôle.
En conséquence, la vue va devoir demander régulièrement au contrôleur de lui donner de nouvelles données. À chaque fois que l'on fait défiler la vue, elle va réclamer de nouvelles données au contrôleur.
Le problème, c'est que ma TableView ne sait pas avec quel contrôleur elle va travailler. Ici, nous utilisons notre ListViewController
qui présente une liste de jouets, mais ailleurs, nous pourrions en utiliser un autre qui fournit des listes de réglages, ou des listes de contacts...
Pour faire fonctionner notre TableView, on doit donc résoudre le problème suivant :
Ma TableView doit pouvoir être informée de la composition de la liste par le contrôleur.
N'importe quel objet doit pouvoir faire ce travail. Ma TableView se moque de savoir avec qui elle travaille, et cela me permet de la réutiliser dans de multiples situations.
Pour résoudre ce double problème, on va utiliser le delegate pattern. On dit en effet que la TableView délègue une partie de son fonctionnement à un autre objet. Et au cœur du delegate pattern, on va voir ce que nous avons étudié dans la partie 1 : un protocole !
Le delegate pattern est une nouvelle méthode de communication aveugle entre la vue et le contrôleur.
Et voici comment cela fonctionne :
On crée une liste de questions que la vue peut poser.
La vue nomme un objet son delegate, en l'occurrence notre contrôleur.
Le contrôleur s'engage à répondre aux questions sur la liste.
Le contrôleur répond effectivement aux questions.
Delegate pattern et protocoles
Détaillons maintenant ces 4 étapes en rentrant dans le détail du protocole.
1. On crée une liste de questions que la vue peut poser
La liste de questions est une liste d'exigences, en fait. Donc ici, on parle de la création d'un protocole. Pour UITableView
, ce protocole se nomme UITableViewDataSource
, car c'est la source de données.
protocol UITableViewDataSource: NSObjectProtocol {
func numberOfSections(in tableView: UITableView) -> Int
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell
// (...)
}
2. La vue nomme un objet son delegate, en l'occurrence notre contrôleur
UITableView
a une propriété dataSource
de type UITableViewDataSource
:
class UITableView: UIScrollView {
weak var dataSource: UITableViewDataSource?
}
Cette propriété est utilisée à l'intérieur de la classe UITableView
pour appeler les méthodes du protocole au moment où la TableView en a besoin. Comme promis, notre TableView se fiche bien de savoir quel objet fera office de dataSource. Elle a juste besoin de savoir qu'il répondra aux exigences de notre protocole.
Ensuite, cette propriété prend pour valeur le contrôleur :
class ViewController: UIViewController {
var tableView: UITableView
override func viewDidLoad() {
super.viewDidLoad()
// J'assigne le contrôleur comme valeur de la propriété dataSource.
tableView.dataSource = self
}
}
3. Le contrôleur s'engage à répondre aux questions sur la liste
Le contrôleur va ici adopter le protocole UITableViewDataSource
:
extension ViewController: UITableViewDataSource {
}
4. Le contrôleur répond effectivement aux questions
Le contrôleur implémente les méthodes de UITableViewDataSource
:
extension ViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 10
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// (...)
}
}
Vous venez de découvrir un nouveau mode de communication aveugle entre la vue et le contrôleur :
Mise en place de la dataSource !
Mettons toute cette théorie en application dans notre code.
2. La vue nomme un objet son delegate, en l'occurrence notre contrôleur
Pour que la TableView nomme le contrôleur sa dataSource, on peut faire de notre TableView un outlet de ListViewController, et ensuite recopier le code que je vous ai donné au-dessus.
Mais nous allons faire encore plus simple en utilisant le control drag !
Faites un control drag dans le storyboard depuis la TableView vers le contrôleur :
Et voilà, vous venez de nommer votre contrôleur, la dataSource de la TableView.
3. Le contrôleur s'engage à répondre aux questions sur la liste
Comme on l'a vu à cette étape, nous allons faire adopter le protocole UITableViewDataSource
à notre contrôleur ListViewController
en passant par une extension :
extension ListViewController:UITableViewDataSource {}
4. Le contrôleur répond effectivement aux questions
Ensuite, nous allons implémenter les méthodes du protocole pour nous y conformer correctement. Nous n'allons en implémenter que trois, car les autres sont optionnelles.
@objc protocol MonProtocole {
optional func maMethodeOptionnelle()
func maMethodeRequise()
}
La première méthode que nous allons implémenter s'appelle numbersOfSection
:
extension ListViewController: UITableViewDataSource {
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
}
Cette méthode permet de préciser à la TableView le nombre de sections dont nous allons avoir besoin. Dans notre cas, nous ne voulons pas séparer notre code selon des sections, donc nous allons simplement renvoyer 1 pour avoir une seule section.
La deuxième méthode se nomme numbersOfRowsInSection
:
extension ListViewController: UITableViewDataSource {
// (...)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ToyService.shared.toys.count
}
}
Elle permet de préciser le nombre de cellules que l'on veut pour chaque section. Dans notre cas, on veut autant de cellules que nous avons d'éléments dans notre tableau de jouets toys
.
La troisième méthode va nous permettre de préciser le contenu de chaque cellule. Je vous propose que l'on voie cela ensemble dans le prochain chapitre !
En résumé
La delegate pattern est une communication aveugle entre la vue et le contrôleur. Elle s'organise en 4 étapes :
On crée une liste de questions que la vue peut poser.
La vue nomme un objet son delegate, en l'occurrence notre contrôleur.
Le contrôleur s'engage à répondre aux questions sur la liste.
Le contrôleur répond effectivement aux questions.
La liste de questions est en fait un protocole auquel se conforme le contrôleur.
Dans l'étape 2, on peut utiliser directement le control drag pour nommer le contrôleur le
dataSource
.
Dans le prochain chapitre, nous allons remplir notre TableView en précisant le contenu de nos cellules !