Note: Ne pas suivre les étapes de ce document, référez-vous plutôt au labo « Les amis de la science » pour le fonctionement de UITableView + segue.
De plus, ne pas utiliser les classes NSArray et NSDictionary – sauf pour
tableauDesVideos = NSArray(contentsOfFile: path) as! Array
Téléchargement des ressources de départ: TIM.FLIX.images
Il faut remettre votre projet (format zip) dans le dossier remise.
Avec ce tutoriel, nous apprendrons à construire une application qui affiche, grace à ‘UITableView’ et une cellule personnalisée, une liste de titres vidéos renseignée par un fichier de propriétés (plist).
Une page contenant le détail de la vidéo sera affichée – par un ‘segue’ – suite à la sélection d’un élément de la table.
Voici le ‘storyboard’:
Savoir créer un tableau de type ‘NSArray’ à partir d’un fichier de propriétés. Comprendre et utiliser la délégation de l’objet ‘UITableView’. Etre capable de préparer et lancer par programmation, une transition vers une autre scène (‘segue’). Savoir programmer une ‘UITableViewCell’ personnalisée (design et classe).
Éléments de contenu:
Le projet de départ: TIMFLIX.swift. depart
À cette étape, nous allons mettre en place les scènes du projet, programmer une transition (segue), de la page de chargement vers la liste, positionner un UITableView puis finalement, renseigner une transition de la liste vers la scène de détail.
Action 1 – Ouvrons le ‘Storyboard’ du projet de départ:
Action 4 – Ajoutons de la couleur, un UITableView ainsi qu’un déplacement (segue) à partir la liste des vidéos vers la scène de détail:
Action 5 – Programmons la transition de la scène ‘Intro’ vers la scène ‘Liste des vidéos’
Note: La programmation de la transition, de la scène d’introduction vers la scène de la liste des vidéos, est réalisée avec les méthodes ‘NSTimer.scheduledTimerWithTimeInterval’ et ‘performSegueWithIdentifier’.
Voici le code à ajouter à la classe de la scène d’introduction:
override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. let timer = NSTimer.scheduledTimerWithTimeInterval(2.0, target: self, selector: Selector("tournerLaPage"), userInfo: nil, repeats: false) } // viewDidLoad func tournerLaPage() { performSegueWithIdentifier("listeVideos", sender:self) } // tournerLaPage
Objectifs:
Commençons par créer un tableau ‘NSArray‘ à partir de la liste de propriétés ‘Listes des videos.plist’. Note, ce fichier est dans le projet de départ.
Voici un extrait du fichier contenant les données des vidéos:
<array> <dict> <key>titre</key> <string>Pirates du cyberspace</string> <key>duree</key> <string>1 h 48 m</string> <key>classement</key> <string>18A</string> <key>pochette</key> <string>pochette-000.jpg</string> <key>annee</key> <string>1995</string> <key>description</key> <string>L'utilisation des ordinateurs lui étant bannie pour des années, un ex-enfant prodige replonge dans le monde du crime informatique avec trois autres pirates.</string> <key>cote</key> <string>4</string> </dict> <dict> <key>titre</key> <string>Star trek: The Next Generation </string> <key>duree</key> <string>1 h 48 m</string> <key>classement</key> <string>18A</string> <key>pochette</key> <string>pochette-007.jpg</string> <key>annee</key> <string>1987</string> <key>description</key> <string>In the year 2364, Capt. Jean-Luc Picard leads the new Enterprise on missions of discovery. First Officer William Riker, engineer Geordi La Forge, and Klingon crewmember Worf join Picard as they explore the universe and interact with alien species.</string> <key>cote</key> <string>4</string> </dict> ... <array>
Ce fichier va nous permettre de créer un tableau ‘NSArray’ qui contiendra des objets de type ‘NSDictionary’
Note: Nous utilisons la classe NSArray au lieu de la classe Array car cette dernière ne propose pas de méthode pour créer un tableau à partir d’un fichier de propriétés.
Nous pourrons accéder aux éléments du tableau avec la syntaxe:
tableauDesVideos[noElement]
et
tableauDesVideos[noElement][« pochette »], …
Action 6 – Modifions la classe de la scène ‘Liste des vidéos’.
Note: nous voulons créer le tableau des vidéos au chargement de la scène ‘Liste des vidéos’, c’est pourquoi nous modifions le fichier ‘ViewController.swift’.
// ViewController.swift // TIMFLIX import UIKit /// Classe de la scène "Liste des vidéos" class ViewController: UIViewController { /// Tableau renseigné par le fichier "Liste des videos.plist" var tableauDesVideos = NSArray() /// Méthode de départ... override func viewDidLoad() { super.viewDidLoad() // Chargement de la liste des vidéos if let path = NSBundle.mainBundle().pathForResource("Listes des videos", ofType: "plist") { tableauDesVideos = NSArray(contentsOfFile: path) } else { println("Erreur: Le chemin vers le fichier de données est invalide ") } // if let path ... // Trace temporaire println(tableauDesVideos) } // viewDidLoad } // class ViewController
Note: Nous pourrions créer un objet de type NSDictionary à partir d’un des éléments du ‘tableauDesVideos’ en procédant comme suit:
let unFilm = tableauDesVideos[0] as NSDictionary let titre = unFilm["titre"] as String println("Le titre du film est: \(titre)")
Action 5.2 – Pratiquer l’accès au tableau
// Définition non typée var tableauDesVideos = [] // Accès typé // Temporaire - tester l'accès au tableau var titre:String var duree:String var pochette:String if let temp = (tableauDesVideos[0] as NSDictionary)["titre"] as? String{ titre = temp } else { titre = ""} // Ceci est valide aussi if let temp = tableauDesVideos[0]["duree"] as? String{ duree = temp } else {duree=""} if let temp = tableauDesVideos[0]["pochette"] as? String{ pochette = temp } else {pochette=""} println("Nom: \(titre),\nDurée: \(duree)\nPochette: \(pochette)")
// Définition typée var tableauDesVideos: Array< Dictionary <String,String> > = [] // Temporaire - tester l'accès au tableau var titre:String var duree:String var pochette:String if let temp = tableauDesVideos[0]["titre"]{ titre = temp } else { titre = ""} if let temp = tableauDesVideos[0]["duree"]{ duree = temp } else {duree=""} if let temp = tableauDesVideos[0]["pochette"]{ pochette = temp } else {pochette=""} println("Nom: \(titre),\nDurée: \(duree)\nPochette: \(pochette)") let imagePochette = UIImageView(image: UIImage(named: pochette)) imagePochette.center = view.center view.addSubview(imagePochette)
Action 5.3 – Modifions la structure du fichier de données pour y ajouter un tableau d’acteurs
<array> <dict> <key>acteurs</key> <array> <dict> <key>nom</key> <string>Bob</string> <key>age</key> <string>99</string> </dict> </array> <key>titre</key> <string>Pirates du cyberspace</string> <key>duree</key> <string>1 h 48 m</string> <key>classement</key> <string>18A</string> <key>pochette</key> <string>netflix-000.jpg</string> <key>annee</key> <string>1995</string> <key>description</key> <string>L'utilisation des ordinateurs lui étant bannie pour des années, un ex-enfant prodige replonge dans le monde du crime informatique avec trois autres pirates.</string> <key>cote</key> <string>4</string> </dict> ... </array>
Action 5.4 – Obtenons le nom du premier acteur
if let temp = (((tableauDesVideos[0] as NSDictionary)["acteurs"] as NSArray)[0] as NSDictionary)["nom"] as? String{ nomActeur = temp } else { nomActeur = ""}
Note: Pour l’accès dynamique vers des structures de type tableau ou dictionnaire, il faut utiliser des classes NS.
Pour être en mesure d’utiliser un objet de type UITableView dans un projet, il faudra se déclarer délégué d’au moins le protocole ‘UITableViewDataSource‘ et au besoin ‘UITableViewDelegate‘.
Etre délégué pour un objet, c’est devoir implémenter des méthodes dont les signatures sont prescrites par la super classe.
Par exemple, pour être en mesure de populer notre UITableView à partir du tableau des vidéos, une de nos classes devra souscrire au protocole ‘UITableViewDataSource’ et programmer les deux méthodes obligatoires suivantes:
func tableView(tableView: UITableView, numberOfRowsInSection: NSInteger) -> Int
func tableView(tableView: UITableView,cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell!
Le fichier.swift (ou .h) d’une classe nous renseigne sur les méthodes de délégation, le niveau de prescription (@required ou @optional) ainsi que sur la syntaxe.
Voici un extrait du fichier UITableView.h
@protocol UITableViewDataSource<NSObject> @required - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section; // Row display. Implementers should *always* try to reuse cells by setting each cell's reuseIdentifier and querying for available reusable cells with dequeueReusableCellWithIdentifier: // Cell gets various attributes set automatically based on table (separators) and data source (accessory views, editing controls) - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath; @optional - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView; // Default is 1 if not implemented ... @protocol UITableViewDelegate<NSObject, UIScrollViewDelegate> @optional ... // Called after the user changes the selection. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath; ...
Une source d’information sur les protocoles et méthodes de délégation d’une classe est la documentation developpeur Apple.
Par exemple, pour UITableView:
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDataSource_Protocol/Reference/Reference.html
https://developer.apple.com/library/ios/documentation/uikit/reference/UITableViewDelegate_Protocol/Reference/Reference.html
Passons à l’action. Dans la prochaine vidéo, nous allons localiser le fichier entête de la classe UITableView pour indentifier et implémeter les méthodes déléguées qui vont nous permettre de renseigner les données à afficher via le UITableView de la scène ‘Liste des vidéos’.
Action 7 – Ajoutons le code suivant au fichier ‘ViewController.swift’:
// Les méthodes de délégation pour un UITableView // ******************************************************************** func tableView(tableView: UITableView, numberOfRowsInSection: NSInteger) -> Int // ******************************************************************** { return tableauDesVideos.count } // numberOfRowsInSection // ******************************************************************** func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell! // ******************************************************************** { var videoCourant = tableauDesVideos[indexPath.row] as NSDictionary var celluleCourante = UITableViewCell() celluleCourante.textLabel.text = videoCourant["titre"] as String celluleCourante.imageView.image = UIImage(named:videoCourant["pochette"] as String) return celluleCourante } // cellForRowAtIndexPath
Action 8 – Traçons un lien entre la propriété ‘dataSource’ de notre objet ‘UITableView’ et le ‘ViewController’ de la scène ‘Liste des vidéos’
Action 9 – Renseignons l’identificateur de UITableViewCell
// ******************************************************************** func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell! // ******************************************************************** { var videoCourant = tableauDesVideos[indexPath.row] as NSDictionary var celluleCourante:UITableViewCell // Créer une cellule de façon écologique if let celluleRecyclee = tableView.dequeueReusableCellWithIdentifier("modeleCellule") as? UITableViewCell { // oui celluleCourante = celluleRecyclee } else { // Non, alors créer une nouvelle cellule à partir de la classe UITableViewCell celluleCourante = UITableViewCell(style: UITableViewCellStyle.Default, reuseIdentifier: "modeleCellule") } // !cell celluleCourante.textLabel.text = videoCourant["titre"] as String celluleCourante.imageView.image = UIImage(named:videoCourant["pochette"] as String) return celluleCourante } // cellForRowAtIndexPath
Action 11 – Il ne reste maintenant plus qu’à définir un lien IBAction sur le bouton de la scène détail (méthode: revenirScenePrecedente) et y ajouter le code suivant:
// DetailVideo.swift @IBAction func retour(sender: AnyObject) { self.dismissViewControllerAnimated(true, completion:nil); } // retour
L’étape 2 complétée: TIMFLIX.swift. etape02
Objectifs:
Étape 3.1
Action 12 – Réalisons le design de la cellule
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell! // ******************************************************************** ... { // **** -> Placer en commentaire les 2 lignes suivantes: //celluleCourante.textLabel.text = videoCourant["titre"] as String //celluleCourante.imageView.image = UIImage(named:videoCourant["pochette"] as String)
Ce qui produit ceci à l’exécution de l’application:
Étape 3.2
Action 15 – Réalisons le design de la scène ‘Détail’
Nous allons ajouter une nouvelle classe à notre projet. Cette classe sera dérivée de la classe UITableViewCell. Elle deviendra la classe de notre cellule personnalisée. Cela nous permettra de définir des liens MVC et de renseigner correctement les éléments visuels à l’intérieur de la méthode ‘cellForRowAtIndexPath: indexPath: ».
Action 16 – Ajoutons une nouvelle classe ‘perso’ pour la cellule du film:
Action 17 – Définissons des liens MVC entre les objets de la cellule et sa classe
// CelluleVideo.swift // projet-TIMFlix-etape03 @IBOutlet var titre: UILabel! @IBOutlet var cote: UIImageView! @IBOutlet var imageVideo: UIImageView! @IBOutlet var annee: UILabel!
Action 18 – Modifications le code du fichier ‘ViewController.swift’ pour y renseigner ces nouveaux objets.
Attention, ce code n’est plus à jour – voir les Amis de la science!!!
// ******************************************************************** func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell! // ******************************************************************** { println(tableauDesVideos[indexPath.row]) var videoCourant = tableauDesVideos[indexPath.row] as NSDictionary var celluleCourante:CelluleVideo let cote = videoCourant["cote"] as String // Créer une cellule de façon écologique if let celluleRecyclee = tableView.dequeueReusableCellWithIdentifier("modeleCellule") as? CelluleVideo { // oui celluleCourante = celluleRecyclee } else { // Non, alors créer une nouvelle cellule à partir de la classe CelluleNetflixCell celluleCourante = CelluleVideo(style: UITableViewCellStyle.Default, reuseIdentifier: "modeleCellule") } // !cell celluleCourante.titre.text = videoCourant["titre"] as String celluleCourante.annee.text = videoCourant["annee"] as String celluleCourante.cote.image = UIImage(named:"\(cote)-star-rating.png") celluleCourante.imageVideo.image = UIImage(named:videoCourant["pochette"] as String) return celluleCourante } // cellForRowAtIndexPath
Objectifs:
Action 19 – Modifions le contenu du fichier ‘DetailVideo.swift’
// // DetailVideo.swift // TIMFLIX // // Created by Alain on 2014-07-16. // Copyright (c) 2014-2015 TIM. All rights reserved. // import UIKit class DetailVideo: UIViewController { var detailsVideo:Dictionary<String,String>? var test:String? @IBOutlet var imageVideo: UIImageView! @IBOutlet var descriptionVideo: UITextView! @IBOutlet var cote: UIImageView! @IBOutlet var titre: UILabel! @IBOutlet var classement: UILabel! @IBOutlet var duree: UILabel! @IBOutlet var annee: UILabel! @IBAction func retour(sender: AnyObject) { self.dismissViewControllerAnimated(true, completion:nil); } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view. // Créer un tabloTempo si detailsVideo!nil println(detailsVideo!) if let _detailsVideo = detailsVideo { // À compléter par l'étudiant // Placer les valeurs reçues à l'écran. } else{ println("Oups, petit problème: aucun detail sur la vidéo...") } } // viewDidLoad override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } /* // #pragma mark - Navigation // In a storyboard-based application, you will often want to do a little preparation before navigation override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) { // Get the new view controller using segue.destinationViewController. // Pass the selected object to the new view controller. } */ }
Action 20 – Définissons un lien MVC sur le « UITableView » de la scène ‘Liste des vidé0’:
@IBOutlet var TViewVideos: UITableView!
Action 21 – Ajoutons le code suivant au fichier ViewController.swift
// Lancée automatiquement avant une transition 'segue' override func prepareForSegue(segue: UIStoryboardSegue!, sender: AnyObject!) { // 1 - pointer sur le controlleur de destination var vc: DetailVideo = segue.destinationViewController as DetailVideo // 2 - Obtenir la cellule sélectionnée // sender -> pointe sur la cellule sélectionnée let selectionCourante = self.TViewVideos.indexPathForCell(sender as UITableViewCell).row; // 3 - renseigner les valeurs à passer vc.detailsVideo = tableauDesVideos[selectionCourante] as? Dictionary <String,String> }
Action 22 – Testons l’application finale
Améliorations à apporter:
L’étape 4 complétée: TIMFLIX.swift.solution (Note, le solutionnaire n’est plus disponible)