Les amis de la science

Introduction à ‘UITableView’ et ‘UICollectionView’

Note: Ce document a été converti vers Xcode 9 et swift 4


 Mise en contexte:

Les classes UITableView et UICollectionView permettent  l’affichage d’une liste d’éléments soit sur une ou sur plusieurs colonnes.
Exemples:

amis-de-la-science-20

UICollectionView


timflix-figure08

UITableView


iOS-preferences
 


UITableView

La classe UITableView permet l’affichage d’une liste d’éléments sur une seule colonne.
UITableView est une sous-classe de UIScrollView, qui permet aux utilisateurs de faire défiler les éléments de la table.
C’est une classe idéale lorsqu’il est nécessaire d’afficher une grande quantité de données sous la forme d’un tableau.
Par défaut, UITableView propose des cellules qui contiennent un titre, un sous-titre, une image à gauche et un accessoire à droite.
Voici un exemple d’un UITableView avec un titre, sous-titre et une image:
tvcellstyle_subtitle
Voici un exemple d’un UITableView avec un accessoire:
tv_plain_style


Les protocoles

UITableView et UICollectionView proposent deux protocoles:  dataSource et delegate (plus prefetchDataSource pour UICollectionView) .
Il est obligatoire de souscrire au protocole dataSource pour lier les contrôles à une source de données.
Par exemple, si nous voulions utiliser un UICollectionView sur une scène, il faudrait ajouter UICollectionViewDataSource à la suite du nom de la super classe  du contrôleur de la scène:

class ViewController: UIViewController, UICollectionViewDataSource {
// et
viewDidLoad(){
   unCV.dataSource = self
}


Sources de données

Les données à afficher, via UITableVite et UICollectionView peuvent provenir de trois sources:

  1. Un tableau codé en dur.
  2. Un tableau créé à partir d’un fichier de donnée – liste de propriétés.
  3. Un tableau créé suite à une requête lancée vers une API web (plist, json, XML, …).

 


Étape1 – Afficher les éléments d’un tableau

À cette étape, nous allons créer une application qui affiche, via un UITableView, les enregistrements d’un tableau (Array) local.
Action 1.0Téléchargeons les ressources du projet
Action 1.1 – Créons un nouveau projet « Les amis de la science »
Action 1.2 –  Ajoutons, dans le groupe ‘Ressources’,  les ressources du projet (téléchargées à l’étape 1.0)
Action 1.3 –  Remplaçons le contenu du fichier ‘ViewController.swift’ par;

//
//  ViewController.swift
//  Les amis de la science
//
//  Créé par Alain Boudreault le 2014-09-28.
//  Copyright (c) 2014-2016 Alain. All rights reserved.
//  ------------------------------------------------------------------
//  m-a-j: 2016.08.23 - conversion du projet vers Xcode 8 et swift 3
//         2016.10.16 - fin de la conversion vers swift 3
//         2017.10.15 - conversion vers Xcode 9 et swift 4
//  ------------------------------------------------------------------
import UIKit
class ViewController: UIViewController {
    // ou bien: Array<Dictionary<String,String>>
    let lesAmisDeLaScienceData:[Dictionary<String,String>] = [
        ["nom":"Albert Einstein",       "photo":"Albert Einstein.jpg",          "texte":"lorem ipsum01 ...", "naissance":"1900"],
        ["nom":"Albert Jacquard",       "photo":"Albert Jacquard.jpg",          "texte":"lorem ipsum02 ...", "naissance":"1900"],
        ["nom":"Blaise Pascal",         "photo":"Blaise Pascal.jpg",            "texte":"lorem ipsum03 ...", "naissance":"1900"],
        ["nom":"Braun",                 "photo":"Braun.jpg",                    "texte":"lorem ipsum04 ...", "naissance":"1900"],
        ["nom":"Christian Huygens",     "photo":"Christian Huygens.jpg",        "texte":"lorem ipsum05 ...", "naissance":"1900"],
        ["nom":"Daniel Fahrenheit",     "photo":"Daniel Fahrenheit.jpg",        "texte":"lorem ipsum06 ...", "naissance":"1900"],
        ["nom":"Dennis Ritchie",        "photo":"Dennis Ritchie.jpg",           "texte":"lorem ipsum07 ...", "naissance":"1900"],
        ["nom":"Galileo Galilei",       "photo":"Galilée.jpg",                  "texte":"lorem ipsum08 ...", "naissance":"1900"],
        ["nom":"Henri Becquerel",       "photo":"Henri Becquerel.jpg",          "texte":"lorem ipsum09 ...", "naissance":"1900"],
        ["nom":"Heinrich Hertz",        "photo":"Hertz.jpg",                    "texte":"lorem ipsum10 ...", "naissance":"1900"],
        ["nom":"Jean Perrin",           "photo":"Jean Perrin.jpg",              "texte":"lorem ipsum11 ...", "naissance":"1900"],
        ["nom":"Karl Guthe Jansky",     "photo":"Karl Guthe Jansky.jpg",        "texte":"lorem ipsum12 ...", "naissance":"1900"],
        ["nom":"Marie Curie",           "photo":"Marie Curie.jpg",              "texte":"lorem ipsum13 ...", "naissance":"1900"],
        ["nom":"James Clerk Maxwell",   "photo":"maxwell.jpg",                  "texte":"lorem ipsum14 ...", "naissance":"1900"],
        ["nom":"Steve Jobs",            "photo":"Steve Jobs.jpg",               "texte":"lorem ipsum15 ...", "naissance":"1900"],
        ["nom":"Wilhelm Conrad Rontgen","photo":"Wilhelm Conrad Rontgen.jpg",   "texte":"lorem ipsum16 ...", "naissance":"1900"],
    ] // lesAmisDeLaScienceData
    // *****************************************************
    override func viewDidLoad()
    // *****************************************************
    {
        super.viewDidLoad()
        if let nomFichierImage = lesAmisDeLaScienceData[0]["photo"] {
            let uneImage = UIImageView(image: UIImage(named: nomFichierImage))
            view.addSubview(uneImage)
        } // if let
    } // viewDidLoad
}  // ViewController

 
Action 1.4 – Testons l’application dans le simulateur.
amis de la science 01
 
Action 1.5 – Analysons le code; le tableau de dictionnaires et l’image chargée par programmation.
 
Note: Par inférence, le tableau est signé [Dictionary <String, String>].  Cela représente le mode d’utilisation le plus simple qui soit.  Il ne sera pas nécessaire de signer les accès.  Par exemple, comme ceci:

(lesAmisDeLaScienceData[2] as Dictionary<String, AnyObject>)[« nom »] as String

 
Action 1.6 – Glissons un UITableView sur la scène
Les amis de la science03
Action 1.7 – Observons les protocoles disponibles pour l’objet UITableView .
Screen Shot 2014-09-28 at 5.36.41 PM
 
Action 1.8 – Souscrivons, le contrôleur de la scène, au protocole ‘dataSource’ du UITableView.
Les amis de la science04
Action 1.9 – Ajoutons le nom du protocole à la suite de la définition de la classe du contrôleur de la scène.
Note: Cette opération est redonnante avec 1.8.  Elle permet d’indiquer à Xcode de nous afficher les méthodes du protocole lorsqu’en mode ‘complétion de code’.

class ViewController: UIViewController, UITableViewDataSource {
// Note: Cette ligne sera en erreur tant que les méthodes obligatoires du
// protocole dataSource ne seront pas déclarées dans la classe du contrôleur.
}

 
Action 1.10 – Mettons en commentaire les lignes suivantes de la méthode ‘viewDidLoad’.

    // *****************************************************
    override func viewDidLoad()
    // *****************************************************
    {
        super.viewDidLoad()
    //let uneImage = UIImageView(image: UIImage(named: lesAmisDeLaScienceData[0]["photo"]!))
    //view.addSubview(uneImage)
    } // viewDidLoad

 


Méthodes obligatoires  du protocole dataSource

Lorsqu’une de nos classes adopte le protocole ‘dataSource’ de la classe ‘UITableView‘  nous avons l’obligation de programmer les deux méthodes suivantes:

La méthode ‘tableView – numberOfRowsInSection’ sert à indiquer au ‘TableView’ le nombre de lignes (enregistrements) à afficher.  Habituellement, cette méthode retourne le nombre d’éléments du tableau des données.
La méthode ‘tableView – cellForRowAtIndexPath’ sert à fournir les données à utiliser pour faire le rendu de la cellule courante.  Cette méthode est appelé automatiquement à chaque fois qu’une cellule doit être affichée à l’écran ou suite à l’utilisation de la méthode UITableView.reloadData().
 
Action 1.11 – Ajoutons les méthodes obligatoires du protocole dataSource à la classe de la scène principale:
tableView->numberOfRowsInSection et tableView->cellForRowAt indexPath

    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return lesAmisDeLaScienceData.count
    } // numberOfRowsInSection
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("#Construction de la cellule numéro: \(indexPath.row)")
        return UITableViewCell()
    } // cellForRowAt

 
Action 1.12 – Testons l’application.
Note: Le TableView affiche ‘lesAmisDeLaScience.count’ cellules vides.
amis de la science 07.png
 
Action 1.13 – Ajoutons le code suivant à la méthode ‘tableView – cellForRowAtIndexPath’.

// test 1
// ----------------------------------------------------------------------
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("#Construction de la cellule numéro: \(indexPath.row)")
        let uneCellule = UITableViewCell()
        uneCellule.textLabel!.text = "Ceci est la description no \(indexPath.row)"
        return uneCellule
    } // cellForRowAt indexPath
// ----------------------------------------------------------------------
// test 2
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("#Construction de la cellule numéro: \(indexPath.row)")
        let uneCellule = UITableViewCell()
        var nomDuScientifique = "n/a"
        if let _nom = lesAmisDeLaScienceData[indexPath.row]["nom"] {
            nomDuScientifique = _nom
        }
        uneCellule.textLabel!.text = nomDuScientifique
        return uneCellule
    } // cellForRowAt indexPath

 
Action 1.14 – Testons l’application.
Screen Shot 2014-09-28 at 6.00.48 PM
 
Action 1.15 – Ajoutons le code suivant à la méthode ‘tableView – cellForRowAtIndexPath’.

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("#Construction de la cellule numéro: \(indexPath.row)")
        let uneCellule = UITableViewCell()
        var nomDuScientifique = "n/a"
        if let _nom = lesAmisDeLaScienceData[indexPath.row]["nom"] {
            nomDuScientifique = _nom
        }
        uneCellule.textLabel!.text = nomDuScientifique
        if let fichierImage = lesAmisDeLaScienceData[indexPath.row]["photo"] {
            uneCellule.imageView!.image = UIImage(named:fichierImage)
        }
        return uneCellule
    } // cellForRowAt indexPath

 
Action 1.16 – Testons l’application.
Screen Shot 2014-09-28 at 6.10.11 PM
 
Note: Si  le défilement ne fonctionne pas correctement, ajustez les contraintes de mise en page du UITableView:
Screen Shot 2014-09-28 at 6.13.42 PM


Une table avec une cellule personnalisée

 
Action 1.17 – Ajoutons un objet ‘UITableViewCell‘ au tableView de la scène.
Les amis de la science09
 
Pour avoir accès aux éléments que nous placerons dans la cellule, il faut y associer une classe perso.
 
Action 1.18 – Ajoutons une nouvelle classe au projet.  Cette classe doit étendre la classe ‘UITableViewCell’.
Screen Shot 2014-09-28 at 6.33.27 PM
 
Action 1.19 – Associons  la classe « Savant » à la nouvelle cellule.
Screen Shot 2014-09-28 at 6.35.09 PM
 
Action 1.19.2 – Renseignons la propriété ‘Identifier’ de la cellule avec la chaine « celluleSavant ».  Cela représente une référence au design de la cellule.
amis-de-la-science-21
 
Action 1.20 – Plaçons les éléments de design dans la cellule et définissons des liens MCV.
Attention, assurons nous que l’assistant éditeur affiche le contenu du fichier Savant.swift.

    @IBOutlet weak var savantNom: UILabel!
    @IBOutlet weak var savantImage: UIImageView!
    @IBOutlet weak var savantTexte: UITextView!
    @IBOutlet weak var savantAge: UILabel!

Screen Shot 2014-09-28 at 7.38.15 PM
 
Action 1.21 – Ajoutons le code suivant à la méthode ‘tableView – cellForRowAtIndexPath’.

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        print("#Construction de la cellule numéro: \(indexPath.row)")
        var uneCellule:Savant
        uneCellule = tableView.dequeueReusableCell(withIdentifier: "celluleSavant") as! Savant
        var nomDuScientifique = "n/a"
        if let _nom = lesAmisDeLaScienceData[indexPath.row]["nom"] {
            nomDuScientifique = _nom
        }
        uneCellule.savantNom.text = nomDuScientifique
        if let fichierImage = lesAmisDeLaScienceData[indexPath.row]["photo"] {
            uneCellule.savantImage.image = UIImage(named:fichierImage)
        }
        // En déballage forcé:
        uneCellule.savantTexte.text = lesAmisDeLaScienceData[indexPath.row]["texte"]
        return uneCellule
    } // cellForRowAt indexPath

 
Action 1.22 – Terminons le design de la cellule.
Screen Shot 2014-09-28 at 8.02.48 PM
Note: À partir de Xcode 9, le UITableView va tenter d’ajuster la hauteur des cellules automatiquement.  Cela peut produire un résultat indésirable.
Il est possible de désactiver ce comportement dans l’inspecteur de taille du UITableView en décochant la propriété ‘Row Height Automatic’ 

 


Étape 2 – Utilisation d’un fichier de données (plist)

À cette étape, nous remplacerons le  tableau codé en dur par un tableau créé à partir d’un fichier de données livré avec l’application.
 
Action 2.2 Observons la structure du fichier ‘amisDelaScience.plist’.  Voir dans le groupe Ressources.Données
amis-de-la-science-01 Action –
 
Action 2.3 – Affichons la source du fichier de données (Ctrl+Clic):
amis-de-la-science-02
 
Résultat:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<array>
	<dict>
		<key>nom</key>
		<string>Albert Einstein</string>
		<key>photo</key>
		<string>Albert Einstein.jpg</string>
		<key>texte</key>
		<string>Il publie sa theorie de la relativite restreinte en 1905, et une theorie de la gravitation dite relativite generale en 1915.</string>
		<key>naissance</key>
		<string>1879</string>
	</dict>
	<dict>
		<key>nom</key>
		<string>Albert Jacquard</string>
		<key>photo</key>
		<string>Albert Jacquard.jpg</string>
		<key>texte</key>
		<string>Il est connu pour ses engagements citoyens, parmi lesquels la defense du concept de la decroissance soutenable, le soutien aux mouvements du logiciel libre, a la langue internationale esperanto, aux laisses-pour-compte et a l'environnement.</string>
		<key>naissance</key>
		<string>1925</string>
	</dict>
...
</array>
</plist>

 
Action 2.4 – Dans le fichier ‘ViewController.swift’, remplaçons la définition (codée au dur) du tableau des amis de la science par:

    // Définition d'un tableau de dictionnaires
    // Le dictionnaire est 'typé' clé = String et contenu = String dans le but d'alléger
    // la syntaxe d'accès aux éléments du dictionnaire.
    var lesAmisDeLaScienceData:Array<Dictionary<String,String>> = []

 
La classe NSArray – du Foundation framework –  propose un méthode qui permet de créer un tableau à partir d’un fichier de propriétés:
 
Action 2.5 – Ajoutons le code suivant à la méthode viewDidLoad.

     override func viewDidLoad()
    {
        super.viewDidLoad()
        let pathFichierPlist = Bundle.main.path(forResource: "amisDelaScience", ofType: "plist")!
        print("#pathFichierPlist: \(pathFichierPlist)")
        // Voir documentation pour Array VS NSArray:
        // https://developer.apple.com/reference/swift/array
        // https://developer.apple.com/reference/foundation/nsarray
        lesAmisDeLaScienceData = NSArray(contentsOfFile: pathFichierPlist) as! Array
    } // viewDidLoad

Note: Remarquez, dans la console, la complexité du chemin d’accès vers le fichier ‘amisDelaScience.plist.
Action 2.6 – Testons notre modification
amis-de-la-science-03
 
Action 2.7 – Calculons l’age du savant avec le code suivant.

//   func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
        // Recette pour obtenir la valeur de l'année courante
        let date = Date()
        let calendar = NSCalendar.current
        let components = calendar.dateComponents([.year], from: date)
        let anneeCourante = components.year
        let anneeNaissance = lesAmisDeLaScienceData[(indexPath as NSIndexPath).row]["naissance"]!
        print("# \(anneeCourante), \(anneeNaissance)\n")
        uneCellule.savantAge.text = "\(anneeCourante! - Int(anneeNaissance)!) ans"

 
Action 2.8 – Testons l’application.
amis-de-la-science-04
 


Étape 3 – CollectionView

La classe UICollectionView permet l’affichage d’une liste d’éléments sur plusieurs colonnes.
Mise à part le fait qu’un UICollectionView ne propose pas une cellule avec des champs par défaut, c-a-d qui faudra utiliser une classe perso pour la cellule, cet objet se programme presque de la même façon qu’un UITableView.
 


Nous allons reprendre l’exercice de l’étape 2 en utilisant un UICollectionView.
Action 3.1 – Ouvrir le storyboard du module 03. Effaçons le UICollectionView.
Action 3.2 – Glissons un objet ‘CollectionView‘, de la librairie vers la scène.
Note: Remarquez qu’il y a déjà une cellule à personnaliser dans le UICollectionView.
amis-de-la-science-05
 
Action 3.3 – Ajustons la taille et la couleur de fond – clear color – du collectionView ainsi que la taille de la cellule.
amis-de-la-science-07
 
Action 3.4 – Élaborons le design de la nouvelle cellule en y ajoutant un ‘UILabel’ pour le nom, une image et une zone de texte au bas.
amis-de-la-science-08
 
Action 3.5 – Souscrivons le contrôleur de la scène au protocole ‘dataSource‘ du UICollectionView.
amis-de-la-science-09
Note: Expliquer l’opération par programmation:

@IBOutlet weak var CVSavant: UICollectionView!
...
viewDidLoad(){
  CVSavant.dataSource  = self

Action 3.6 – Ajoutons le nom du protocole à la suite de la définition de la classe du contrôleur de la scène.
Note: Cette opération est redonnante avec 3.5.  Elle permet d’indiquer à Xcode de nous afficher les méthodes du protocole lorsqu’en mode ‘complétion de code’.

class ViewController: UIViewController, UICollectionViewDataSource {
// Note: Cette ligne sera en erreur tant que les méthodes obligatoires du
// protocole ne seront pas déclarées dans la classe du contrôleur.

 
Action 3.– Ajoutons les méthodes  ‘collectionView: numberOfItemsInSection’ et ‘collectionView:  cellForItemAtIndexPath’ à la classe de la scène principale.
amis-de-la-science-10
 
Action 3.8 – Ajoutons le code suivant aux nouvelles méthodes.

    //MARK:- Méthodes du protocole UICollectionViewDataSource
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        return lesAmisDeLaScienceData.count
    } // numberOfItemsInSection
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        return UICollectionViewCell() // Va faire planter l'application!
    } // cellForItemAt indexPath

 
Action 3.9 – Testons l’app.
Note: Il y aura une erreur à l’exécution.

2014-09-30 12:51:08.025 Les amis de la science[36210:2367199] *** Assertion failure in -[UICollectionView _createPreparedCellForItemAtIndexPath:withLayoutAttributes:applyAttributes:],/SourceCache/UIKit_Sim/UIKit-3318/UICollectionView.m:1315
2014-09-30 12:51:08.028 Les amis de la science[36210:2367199] *** Terminating app due to uncaught exception ‘NSInternalInconsistencyException’, reason: ‘the cell returned from -collectionView:cellForItemAtIndexPath: does not have a reuseIdentifier – cells must be retrieved by calling -dequeueReusableCellWithReuseIdentifier:forIndexPath:’
 

Explication –  Il faut absolument recycler les cellules avec un  UICollectionView:  dequeueReusableCellWithReuseIdentifier.
Il faudra donc;

  1. Ajouter une nouvelle classe, dérivée de UICollectionViewCell, à notre projet.
  2. Renseigner la propriété ‘Class’ de la cellule.
  3. Renseigner la propriété ‘Identifier’ de la cellule.
  4. Créer la cellule avec  la méthode ‘collectionView.dequeueReusableCellWithReuseIdentifier’.

 
Action 3.10 –  Ajoutons une classe, dérivée de UICollectionViewCell, pour la cellule.
amis-de-la-science-11
 
Action 3.11 – Renseignons la propriété ‘Class’ de la cellule.
amis-de-la-science-12
 
Action 3.12 – Renseignons la propriété ‘Identifier’ de la cellule avec la chaine suivante: « modeleCelluleSavant ».
amis-de-la-science-13
 
Action 3.13 – Ajoutons le code suivant à la méthode ‘collectionView:  cellForItemAtIndexPath‘.

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        var celluleCourante:CVCSavant
        celluleCourante = collectionView.dequeueReusableCell(withReuseIdentifier: "modeleCelluleSavant", for:indexPath) as! CVCSavant
        return celluleCourante
    } // cellForItemAt indexPath

 
Action 3.14 – Testons l’application.
amis-de-la-science-14
Note: Le modèle est maintenant utilisée pour le rendu des cellules.
 
Action 3.15 – Ajoutons les déclarations MVC suivantes au fichier ‘CVCSavant.swift’

//  CVCSavant.swift
import UIKit
class CVCSavant: UICollectionViewCell {
    @IBOutlet weak var savantNom: UILabel!
    @IBOutlet weak var savantImage: UIImageView!
    @IBOutlet weak var savantTexte: UITextView!
}  // class CVCSavant

 
Action 3.16 – Associons les déclarations aux objets dans la cellule.
amis-de-la-science-15
 
Action 3.17 – Ajoutons le code suivant à la méthode ‘collectionView:  cellForItemAtIndexPath‘.

//    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
// ...
        // Renseigner les élements de la cellule courante
        // Recette pour obtenir la valeur de l'année courante
        let date = Date()
        let calendar = NSCalendar.current
        let components = calendar.dateComponents([.year], from: date)
        let anneeCourante = components.year
        let anneeNaissance = lesAmisDeLaScienceData[(indexPath as NSIndexPath).row]["naissance"]!
        let age = " - \(anneeCourante! - Int(anneeNaissance)!) ans"
        celluleCourante.savantNom.text = lesAmisDeLaScienceData[indexPath.row]["nom"]! + age
        celluleCourante.savantTexte.text = lesAmisDeLaScienceData[(indexPath as NSIndexPath).row]["texte"]!
        celluleCourante.savantImage.image = UIImage(named: lesAmisDeLaScienceData[(indexPath as NSIndexPath).row]["photo"]!)
//        return celluleCourante
//    } // collectionView: cellForItemAtIndexPath

 
Action 3.18 – Testons l’application.
amis-de-la-science-16
 
Action 3.19 –  À vous d’ajuster le design.
amis-de-la-science-17
 


Plusieurs modèles de cellules

Il est possible d’utiliser plusieurs modèles de rendu pour les cellules personnalisées de UITableView et UICollectionView.
Action 3.20.a – Copions – atl+glisser – la cellule du UICollectionVue.
Attention, assurez-vous que la cellule est bien sélectionnée.  Au besoin, utilisez l’explorateur de document.
Action 3.20.b – Corrigeons  la propriété ‘Identifier’ de la nouvelle cellule pour ‘modeleCelluleSavant2’.
Note: Il n’est pas nécessaire de renseigner la propriété ‘class’, la nouvelle cellule possède toutes les propriétés de la première.

amis-de-la-science-18

Action 3.21 – Modifions le design du nouveau modèle.
amis-de-la-science-19
 
Action 3.22 – Modifions le code suivant.

//    func collectionView(collectionView: UICollectionView, cellForItemAtIndexPath indexPath: NSIndexPath) -> UICollectionViewCell {
        // Recyclage obligatoire pour un UICollectionViewCell
        let nomModele = indexPath.row % 2 == 0 ? "modeleCelluleSavant":"modeleCelluleSavant2"
        celluleCourante = collectionView.dequeueReusableCell(withReuseIdentifier: nomModele, for:indexPath) as! CVCSavant

 
Action 3.23 – Testons l’application.
amis-de-la-science-20
 


Laboratoire à réaliser par l’étudiant avant l’étape 4

Mes amis – Fichiers de départ
 


Étape 4 – Segue

 
À cette étape nous allons programmer, suite à la sélection d’une cellule par l’utilisateur, un déplacement – segue – vers une scène affichant les informations de la sélection courante.
 
Action 4. –  Ajoutons et élaborons une scène, pour les informations du savant,
amis-de-la-science-etape4-01
 
Action 4.2 – Ajoutons une nouvelle classe au projet pour la scène ‘Détails’ nommée ‘VCDetails’
amis-de-la-science-etape4-06
 
Action 4.3 – Associons  la classe ‘VCDetails’ à la scène ‘Détails’.
amis-de-la-science-etape4-07
 
Action 4.4 – Définissons un @IBAction entre le bouton de retour en arrière et la classe de la scène ‘Détails’.
amis-de-la-science-etape4-08
 
Action 4.5 – Ajoutons, à la méthode ‘retourALaListe’  le code suivant:

        dismiss(animated: true, completion: nil)

 


Définition des liens segues

 
Pour définir un segue entre une cellule d’un tableView ou d’un CollectionView, il suffit de ‘Ctrl+Glisser’  la cellule vers la View de la scène de destination.
Note: S’il y a plusieurs cellules, il faut alors recommencer cette opération pour toutes les cellules restantes.
 
Action 4.6 – Définissons un segue de type ‘present modally’, à partir de la première cellule, vers la scène ‘Détails’.
amis-de-la-science-etape4-02b
 
 
amis-de-la-science-etape4-03
 
Action 4.7 – Recommençons la manœuvre sur la deuxième cellule.
amis-de-la-science-etape4-04b
 
Action 4.8 – Observons le résultat obtenu.
amis-de-la-science-etape4-05b
 
Action 4.9 – Testons l’application.
amis-de-la-science-etape4-09
 


PrepareForSegue

La classe UIViewController propose la méthode ‘prepareForSegue’ comme moyen de passer des informations à la scène de destination lors d’un segue.
Celle méthode est programmée dans le contrôleur de la scène de départ; celle qui présente le TableView ou le CollectionView.
Voici sa signature:

override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?)

 
La méthode reçoit (lors de l’appel) une référence vers la destination ainsi qu’une référence sur l’objet – habituellement une cellule – ayant reçu l’événement déclencheur du segue.
 
Action 4.10 – Ajoutons le code suivant au contrôleur de la scène principale:

    // Méthode exécutée automatiquement avant un segue
    //MARK:- Préparer le déplacement (segue)
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        print("# Exécution de la méthode: prepare:for segue\n")
    } // prepare(for segue: ...)

 
Action 4.11 – Testons l’application (faire plusieurs sélections).
amis-de-la-science-etape4-10
 
Action 4.12 – Définissons un @IBOutlet sur le collectionView.
amis-de-la-science-etape4-11
 


indexPathForCell

 
Les classes UITableView et UICollectionView proposent la méthode ‘indexPathForCell(uneCellule)’ pour connaitre la position d’une cellule.
Lors de l’exécution de la méthode ‘prepareForSegue’, nous recevons une référence (sender: Any?) à la cellule sélectionnée.
Par conséquent,   le code suivant permet de connaitre l’indice de la cellule sélectionnée:
 
Action 4.13 – Ajoutons le code suivant à la méthode ‘prepareForSegue‘.

    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Note: Il faut avoir renseigné un @IBOutlet sur le UICollectionView
        let selection = CVSavant.indexPath(for: sender as! UICollectionViewCell)!.row
        print("# Exécution de la méthode: prepareForSegue pour la cellule numéro: \(selection)\n")
    } // prepare(for segue: ...)

 
Action 4.14 – Testons l’application.
amis-de-la-science-etape4-12
 


segue.destinationViewController

 
Il nous reste maintenant à passer les informations de la sélection courante à la scène de détails.
La méthode ‘prepareForSegue‘ nous renseigne aussi sur la destination:  (segue: UIStoryboardSegue).

segue.destinationViewController -> pointe sur l’instance de classe du contrôleur de la scène de destination.

Donc, avec le code suivant, nous aurons accès aux propriétés publiques du contrôleur de la scène de destination:

var destination = segue.destinationViewController as VCDetails
destination.uneVariable = …

 

Action 4.15 –  Ajoutons le code suivant au contrôleur VCDetails.

class VCDetails: UIViewController {
    // Propriété pour recevoir les informations de la sélection
    var informationsDuSavantCourant =  Dictionary<String,String> ()
    override func viewDidLoad() {
        super.viewDidLoad()
        print("# Nous avons reçu les données suivantes:\(informationsDuSavantCourant)\n")
    } // viewDidLoad

 
 Action 4.16 – Ajoutons le code suivant à la méthode prepareForSegue  de ViewController.swift.
 

    //MARK:- Préparer le déplacement (segue)
    override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
        // Note: Il faut avoir renseigné un @IBOutlet sur le UICollectionView
        // 1 - Déterminer l'index de la sélection à partir de la cellule reçue en paramètre (sender)
        let selection = CVSavant.indexPath(for: sender as! UICollectionViewCell)!.row
        yo(dans: String(selection))
        print("# Exécution de la méthode: prepareForSegue pour la cellule numéro: \(selection)\n")
        // 2 - Créer un objet pointant sur l'instance de classe de la destination
        let destination = segue.destination as! VCDetails
        // 3 - Passer les informations au controleur de destination
        destination.informationsDuSavantCourant = lesAmisDeLaScienceData[selection]
    } // prepare(for segue: ...)

 
 
Action 4.17 – Testons l’application.
amis-de-la-science-etape4-13
 
Action 4.18 – En faire en laboratoire:  Vous devez définir des IBOutlet sur les objets de la scène ‘Détails’ et afficher, sur la scène, les informations reçues.
 
Résultat:
 
amis-de-la-science-etape4-14
 


5 – Extra

Et si le fichier de données provenait du monde exterieur?
Par exemple, au bout d’un URL comme, http://prof-tim.cstj.qc.ca/cours/xcode/sources/amisDelaScience.plist
Action 5.1 – Ajoutons le code suivant à la méthode ‘viewDidLoad’:

        let strURL = "http://prof-tim.cstj.qc.ca/cours/xcode/sources/amisDelaScience.plist"
        if let uneURL = URL(string: strURL) {
            if let _données = NSArray(contentsOf: uneURL) as? Array<Dictionary<String, String>> {
                lesAmisDeLaScienceData = _données
                print(_données)
            } else
            {
                print("\n\n#Erreur: impossible de créer un tableau à partir du fichier... \(strURL)\n\n")
            } // Problème avec l'URL
        }  else {
            print("\n\n#Erreur: impossible de lire les données via \(strURL)\n\n")
        }

Note: Depuis iOS 10, la sécurité a été augmenté.
Il faut permettre les connexions arbitraires de l’application:
Action 5.2 – . Ajoutons la clé suivante dans le fichier info.plist

<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>


solution du laboratoire
 
 
 
 
 


 
Fin du document

Préparé par Alain Boudreault, aka VE2CUY, aka puyansude – 2014.09 – révision du 2016.10.16 pour Xcode 8 et swift 3