Bienvenue dans cette troisième partie ! Dans cette partie, nous allons améliorer les fonctionnalités de notre TableView. Et dans ce chapitre, nous allons commencer par l'ajout de plusieurs catégories de cadeaux.
Quoi de neuf ?
Je vous propose de prendre quelques minutes pour parcourir les petits changements que j'ai apportés au projet, pour que vous ne soyez pas perdu pour la suite.
La principale modification est l'ajout de la possibilité de créer dans la liste des objets de trois catégories différentes :
Voyage (Trip).
Livre (Book).
Jouet (Toy).
Pour cela, j'ai ajouté une page qui permet de choisir la catégorie, et qui redirige vers trois pages différentes, une pour chaque catégorie d'objet à créer.
Voilà ce que cela donne dans le détail :
Vue
Dans le fichier Main.storyboard,
vous pouvez voir les nouvelles interfaces que je viens de mentionner.
Modèle
J'ai ajouté dans le modèle deux autres structures pour nos nouvelles catégories Trip
et Book
.
// Trip.swift
struct Trip {
var departure = ""
var destination = ""
var durationInDays = 0
}
// Book.swift
struct Book {
var title = ""
var author = ""
}
Contrôleur
1/ ListViewController
a à peine changé. J'ai juste rajouté une méthode pour avoir un unwind segue.
@IBAction func unwindToListVC(segue: UIStoryboardSegue) {}
2/ Dans le même esprit, j'ai à peine changé le code ToyViewController
. Maintenant, il utilise directement l'unwind segue pour revenir directement à la liste lors de l'appui sur le bouton save
:
@IBAction func save() {
// (...)
performSegue(withIdentifier: "toyToListUnwind", sender: nil)
}
3/ J'ai rajouté deux contrôleurs, TripViewController
et BookViewController
, qui permettent respectivement de gérer les pages de création d'objets Trip
et Book
. Ils sont extrêmement similaires à ToyViewController
.
La seule grosse différence est que je n'ai pas su comment ajouter des Book
ou des Trip
à notre liste, car ils ne sont pas du type Toy
. Donc nous allons faire cela ensemble.
4/ Enfin, par acquit de conscience, j'ai ajouté un CategoryViewController
, mais il est vide, car toute la navigation est faite dans le storyboard directement.
Une liste hétérogène
Maintenant que nous avons notre projet bien en main, attaquons la suite ! Notre objectif est de rajouter dans la liste nos objets Trip
et Book
qui, pour l'instant, ne sont pas gérés.
Le problème est qu'il faut donc créer un tableau avec trois types différents : Toy
, Book
et Trip
. Comme vous le savez, ce n'est pas possible... sauf si vous avez bien suivi la partie 1 de ce cours !
On va utiliser les protocoles !
Bien ! Je vois que certains suivent... ou font semblant ! Nous allons en effet utiliser les protocoles.
Nous allons créer un protocole Present
qui va nous permettre de regrouper nos trois types d'objets derrière un même type.
protocol Present {}
Pour l'instant, nous n'avons pas besoin de préciser les exigences de ce protocole. On veut juste un type.
Maintenant, nous allons faire adopter le protocole à nos trois types :
// Trip.swift
extension Trip: Present {}
// Book.swift
extension Book: Present {}
// Toy.swift
extension Toy: Present {}
Désormais, nos trois types adoptent Present
, et peuvent donc être regroupés derrière ce nouveau type.
Nous pouvons maintenant modifier notre classe ToyService
pour utiliser Present
:
// AVANT
class ToyService {
static let shared = ToyService()
private init() {}
private(set) var toys = [Toy]()
func add(toy: Toy) {
toys.append(toy)
}
}
// APRÈS
class PresentService {
static let shared = PresentService()
private init() {}
private(set) var presents = [Present]()
func add(present: Present) {
presents.append(present
}
}
Ma liste est maintenant une liste de cadeaux ( presents
) et non de jouets. Pour être cohérent, j'ai décidé de modifier le nom de ma classe en PresentService
.
Nous allons maintenant utiliser cette classe dans nos trois contrôleurs d'ajout d'objets.
// TripViewController.swift
@IBAction func save() {
// (...)
let trip = Trip(departure: departure, destination: destination, durationInDays: Int(daysStepper.value))
PresentService.shared.add(present: trip)
// (...
}
// BookViewController.swift
@IBAction func save() {
// (...)
let book = Book(title: title, author: author)
PresentService.shared.add(present: book)
// (...)
}
// ToyViewController.swift
@IBAction func save() {
// (...)
let toy = Toy(name: name, brand: brand)
PresentService.shared.add(present: toy)
// (...)
}
Et voilà ! Nous avons réussi à remplir notre liste avec des objets de types différents en utilisant un protocole !
Ajouter des exigences
Hé, mais j'ai des erreurs dans mon code !
Ah oui... J'ai oublié ListViewController
. La boulette ! Cette classe utilise toujours ToyService
, que nous venons de remplacer par PresentService
.
Ce n'est pas grave, ce n'est qu'un petit changement rapide. Faisons-le !
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return PresentService.shared.presents.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "ToyCell", for: indexPath)
let present = PresentService.shared.presents[indexPath.row]
cell.textLabel?.text = present.name
cell.detailTextLabel?.text = present.brand
return cell
}
Mais j'ai encore des erreurs !
Décidément, je ne suis pas réveillé... En effet, notre type Present
n'a pas de propriété name
ou brand
. Il n'a d'ailleurs pas de propriété du tout. Or, on va en avoir besoin pour remplir notre cellule.
On va donc rajouter deux propriétés à notre protocole Present
:
protocol Present {
var description: String { get }
var detail: String { get }
}
Ces deux propriétés vont nous permettre d'afficher deux informations sur chaque label de notre cellule :
cell.textLabel?.text = present.description
cell.detailTextLabel?.text = present.detail
Et avant que vous me tombiez dessus avec de nouvelles erreurs, j'anticipe : maintenant que nous avons rajouté des exigences à notre protocole, il faut que nos trois objets s'y conforment.
L'objectif ici est de répondre à la question : Que veulent dire description
et detail pour Trip
, Book
et Toy
?
// Trip.swift
extension Trip: Present {
var description: String {
return departure + " - " + destination
}
var detail: String {
return "\(durationInDays) Days"
}
}
// Book.swift
extension Book: Present {
var description: String
return title
}
var detail: String {
return author
}
}
// Toy.swift
extension Toy: Present {
var description: String {
return name
}
var detail: String {
return brand
}
}
PAUSE ! Il faut admirer ce que vous venez de faire. Vous avez créé ce que l'on appelle une interface. Votre TableView peut maintenant manipuler un objet Present
qui a deux propriétés très claires alors qu'en fait, se cachent derrière trois types qui ont des propriétés très différentes les unes des autres. Present
est donc bien une interface entre les trois types d'un côté et ListViewController
de l'autre.
Cette méthode est extrêmement utile pour supprimer la dépendance d'une classe envers une autre. Ici, ListViewController
ne connaît même pas l'existence de Trip
, Book
et Toy
, car il n'en a pas besoin. Il n'a accès qu'au minimum d'informations que lui fournit Present
.
En résumé
Les protocoles permettent de gérer des collections de type hétérogène.
Les protocoles permettent de créer des interfaces qui peuvent uniformiser des types diversifiés, afin de rendre leur utilisation plus simple et d'en supprimer la dépendance.
Dans le prochain chapitre, nous allons sortir nos pinceaux pour magnifier nos cellules qui, pour l'instant, gâchent un peu la beauté de notre interface !