TIM.Flix.swift

Laboratoire à réaliser en classe

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.


Description

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’:

Figure 1


Ainsi qu’une vidéo du résultat final:

Objectifs

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
 


Étape 1 – Design et navigation

À 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:

figure 2


 
Action 2 – Ajoutons deux nouvelles scènes avec leur classe: Intro.swift et DetailVideo.swift:
 
timflix-figure02

figure 3


Action 3 –  Renseignons la propriété ‘Is initial View Controller’ de la scène ‘Intro’

Vidéo étape01


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:

timflix-figure03

figure 4

 Vidéo étape01.1


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

 


Étape 2 – Afficher la liste des vidéos

Objectifs:

Étape 2.1 – Charger les données

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&apos;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&apos;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.
 

Vidéo étape02.1.5

Étape 2.2 – Renseigner et programmer la délégation de l’objet UITableView

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’
 

timflix-figure04

figure 5


 
Si nous testons l’application nous allons constater que le bouton, permettant de passer à la scène de détail n’est pas affiché.  Il n’est donc pas possible de lancer la transition suite à la sélection d’un item de la liste.
Cela s’explique par le fait que nous n’avons pas utilisé le modèle de la cellule personnalisée pour dessiner les cellules.  Dans ce cas, le UITableView utilise le modèle par défaut de UITableViewCell.  Ce modèle possède des propriétés visuelles comme celles que nous avons utilisées précédemment; celluleCourante.imageView, celluleCourante.textLabel …
Pour utiliser le modèle personnalisé de notre UITableViewCell il faut:

  1. Donner un identificateur à la cellule.
  2. Créer la cellule avec la méthode ‘dequeueReusableCellWithIdentifier’ du paramètre ‘tableView’

 
Action  9 – Renseignons l’identificateur de UITableViewCell

timflix-figure06

figure 6


 
Action 10 – Modifions la méthode ‘cellForRowAtIndexPath: indexPath:’ du fichier ‘ViewController.swift’
Attention, ce code n’est plus à jour – voir les Amis de la science!!!

// ********************************************************************
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

 Résultat final de l’étape 2


L’étape 2 complétée: TIMFLIX.swift. etape02


 

Étape 3 – Personnaliser la cellule du UITableView

Objectifs:

 
Étape 3.1
Action 12 – Réalisons le design de la cellule

timflix-figure06.5

figure 7


Action 13 – Testons l’application
timflix-figure07

figure 8


Note: Les objets visuels du modèle de cellule par défaut entrent en conflits avec nos éléments de design.
Action 14 – Pour l’instant, plaçons les lignes suivantes en commentaire:

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:
TIM.FLIX-etape3.figure9
Étape 3.2
Action 15 – Réalisons le design de la scène ‘Détail’
 

timflix-figure09

figure 10


 

Étape 3.3 – Classe personnalisée pour notre ‘UITableViewCell’

 
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:
TIM.Flix.etape3.3.figure2 TIM.Flix.etape3.3.figure1
 

 
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

 


 

Étape 4 – Programmer la scène des détails du film

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)


 
 

Didacticiel préparé par Alain Boudreault, enseignant en Techniques d’intégration multimédia du cégep de Saint-Jérôme.  Version 1.2 – Révision 2015.10.05