Développer des Apps iOS 8 avec Swift

Partie 6 : Interaction avec des vues multiples

Cette section a été complètement remise à jour pour prendre en compte les changements de Xcode 6.3, à partir du 16 avril 2015.

Au long des parties 1 à 5 nous avons abordé des notions de base de Swift, et construit un projet de démonstration qui crée une Table View et y insère des données issues de l'API iTunes. Si vous ne l'avez pas encore lue, voyez la Partie 1http://jamesonquave.developpez.com/tutoriels/swift/debuter-developper-applications-ios-8/introduction-hello-world/.

Autrement, si vous voulez juste commencer à ce point, téléchargez le code de la Partie 5 pour continuer. Nous l'utiliserons comme patron de départ.

Dans ce tutoriel nous avons un bon nombre de choses à faire, alors démarrons tout de suite !

Les commentaires et les suggestions d'amélioration sont les bienvenus, alors, après votre lecture, n'hésitez pas. 11 commentaires Donner une note à l'article (5).

Article lu   fois.

Les deux auteur et traducteur

Site personnel

Traducteur : Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Modification de l'API Controller

Pour commencer, notre vraie intention pour cette app est d'afficher les informations iTunes sur la musique. Alors, adaptons le code de notre contrôleur API Controller pour mieux manipuler cette information.

Il y a cependant quelque chose qui m'a toujours préoccupé. Quand nous créons notre API Controller, nous n'attachons son délégué qu'une fois qu'il est créé. Mais un contrôleur sans un délégué n'est pas super utile, alors pourquoi donc avoir l'option d'en créer un ?

Changeons le constructeur pour prendre le délégué comme unique argument.

 
Sélectionnez
1.
2.
3.
init(delegate: APIControllerProtocol) {
    self.delegate = delegate
}

Du coup, notre variable delegate dans la classe APIController ne sera plus une optionnelle. Il n'y a pas d'APIController sans un délégué !

Changez aussi la propriété delegate en un objet régulier et non Optionnel de type APIControllerProtocol.

 
Sélectionnez
1.
var delegate: APIControllerProtocol

Vous verrez aussi une erreur dans la méthode searchItunesFor vers la fin, car nous traitons le délégué comme une optionnelle, mais il ne l'est plus. Alors, modifiez la ligne erronée afin qu'elle devienne :

 
Sélectionnez
1.
self.delegate.didReceiveAPIResults(results)

La seule différence est que nous avons enlevé le ? qui suit la propriété delegate, pour indiquer qu'elle n'est pas une optionnelle.

Maintenant dans notre contrôleur SearchResultsController, nous avons quelques modifications à faire. Pour commencer, puisque notre constructeur de l'APIController requiert à présent un objet délégué avant qu'il puisse lui-même être instancié, nous devons le changer en une optionnelle implicite.

Alors dans la déclaration de la variable api changez-la en :

 
Sélectionnez
1.
var api : APIController!

Dans la méthode viewDidLoad nous devons déplier l'objet api afin d'appeler la fonction searchItunesFor(). Voici ce que vous devriez avoir :

 
Sélectionnez
1.
2.
3.
4.
5.
override func viewDidLoad() {
    super.viewDidLoad()
    api = APIController(delegate: self)
    api.searchItunesFor("JQ Software")
}

II. Recherche d'albums

Modifions aussi notre appel de fonction searchItunesFor() dans APIController, pour chercher un terme musical. Nous allons aussi afficher un indicateur networkActivityIndicator, pour informer l'utilisateur qu'une opération réseau a lieu. Cela apparaîtra tout en haut de l'écran du téléphone, dans la barre de statut.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
override func viewDidLoad() {
    super.viewDidLoad()
    api = APIController(delegate: self)
    UIApplication.sharedApplication().networkActivityIndicatorVisible = true
    api.searchItunesFor("Beatles")
}

Puis dans notre chaîne urlPath du contrôleur APIController, modifions les paramètres de l'API pour rechercher spécifiquement des albums.

 
Sélectionnez
1.
let urlPath = "https://itunes.apple.com/search?term=\(escapedSearchTerm)&media=music&entity=album"

Nous allons maintenant obtenir des résultats sous forme de données Album, mais ce format est un peu différent des apps. De fait, lancer l'application à ce stade va causer un plantage, puisque les données JSON attendues n'y sont pas. C'est du code très fragile, mais nous pouvons le rendre un tout petit peu moins fragile en faisant un modèle des données auxquelles nous nous attendons.

III. Créer un modèle Swift pour les albums d'iTunes

Pour faciliter le passage d'informations sur les albums, nous devrions créer un modèle précis de ce qu'est un album. Créez un nouveau fichier Swift et nommez-le Album.swift avec le contenu suivant :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
import Foundation
 
struct Album {
    let titre: String
    let prix: String
    var miniatureImageURL: String
    var largeImageURL: String
    var itemURL: String
    var artisteURL: String
     
    init(nom: String, prix: String, miniatureImageURL: String, largeImageURL: String, itemURL: String, artisteURL: String) {
        self.titre = nom
        self.prix = prix
        self.miniatureImageURL = miniatureImageURL
        self.largeImageURL = largeImageURL
        self.itemURL = itemURL
        self.artisteURL = artisteURL
    }
}

C'est une struct assez simple, elle ne fait que stocker quelques propriétés à propos des albums pour nous. Nous déclarons les six différentes propriétés comme chaînes de caractères, et ajoutons un initialiseur qui initialise toutes les propriétés en fonction de nos paramètres.

Et maintenant que nous avons une struct pour les objets album, utilisons-la !

IV. Utiliser notre nouveau modèle d'album

De retour vers notre SearchResultsController, changeons la variable tableau tableData, et remplaçons-la avec un tableau Swift natif contenant des Albums. En Swift, c'est aussi simple que :

 
Sélectionnez
1.
var albums = [Album]()

Nous pouvons supprimer la ligne var tableData = [], nous n'allons plus l'utiliser. Cela crée un tableau vide qui n'accueillera que des objets Album. Nous allons maintenant changer la dataSource de tableView et ses méthodes déléguées de façon à traiter les albums.

Dans la méthode numberOfRowsInSection, changeons le nombre d'éléments en un décompte des albums dans notre tableau albums :

 
Sélectionnez
1.
2.
3.
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return albums.count
}

Puis dans cellForRowAtIndexPath, remplaçons ces multiples accès de dictionnaire avec une simple recherche d'album :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as! UITableViewCell
    let album = self.albums[indexPath.row]

    // Obtenez le prix formaté en chaîne, pour l'afficher dans le sous-titre
    cell.detailTextLabel?.text = album.prix
    // Mettez à jour le texte de textLabel pour utiliser le titre provenant du modèle de l'album
    cell.detailTextLabel?.text = album.titre
    // Commencez par configurer l'image de la cellule à partir d'un fichier statique
    // Sans cela, nous allons nous retrouver sans une prévisualisation de l'image !
    cell.imageView?.image = UIImage(named: "Blank52")
    let miniatureURLString = album.miniatureImageURL
    let miniatureURL = NSURL(string: miniatureURLString)!
       
    // Si cette image se trouve déjà en cache, ne plus la recharger
    if let img = imageCache[miniatureURLString] {
        cell.imageView?.image = img
    }
    else {
        // L'image n'existe pas en cache, la télécharger
        // Nous devrions faire cela dans un thread d'arrière-plan
        let request: NSURLRequest = NSURLRequest(URL: miniatureURL)
        let mainQueue = NSOperationQueue.mainQueue()
        NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in
            if error == nil {
                // Convertir les données téléchargées en un objet UIImage
                let image = UIImage(data: data)
                // Stocker l'image dans notre cache
                self.imageCache[miniatureURLString] = image
                // Mettre à jour la cellule
                dispatch_async(dispatch_get_main_queue(), {
                    if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {
                        cellToUpdate.imageView?.image = image
                    }
                })
            }
            else {
                println("Error: \(error.localizedDescription)")
            }
        })   
    }
    return cell
}

Ensuite, il y a la méthode didSelectRowAtIndexPath qui devrait être modifiée pour utiliser le tableau d'albums. Mais, comme en réalité nous n'en aurons plus besoin, effaçons juste la méthode entière.

V. Créer des objets Album depuis JSON

À ce point, tout ça ne nous avance pas beaucoup si nous ne créons pas d'informations d'album pour commencer. Nous devons modifier notre méthode didReceiveAPIResults dans SerchResultsViewController pour recevoir des results d'album, créer des objets Album à partir de la réponse JSON, et les enregistrer dans le tableau d'albums. Puisque nous avons maintenant un modèle pour les albums, il semble raisonnable de déplacer cette fonctionnalité dans le modèle Album lui-même. Alors, faisons un petit ajustement à la méthode didReceiveAPIResults et déléguons la responsabilité de construire le tableau des albums à la struct Album.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
func didReceiveAPIResults(results: NSArray) {
    dispatch_async(dispatch_get_main_queue(), {
        self.albums = Album.albumsWithJSON(results)
        self.appsTableView!.reloadData()
        UIApplication.sharedApplication().networkActivityIndicatorVisible = false
    })
}

Remarquez que puisque c'est là que l'appel d'API se conclut, nous désactivons aussi l'indicateur networkActivityIndicator.

Ensuite dans le fichier Album.swift, il nous faut ajouter une méthode statique qui crée une liste d'albums depuis une liste JSON.

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
static func albumsWithJSON(results: NSArray) -> [Album] {
    // Créer un tableau d'albums vide, à remplir depuis cette liste
    var albums = [Album]()

    // Stocker les résultats dans notre tableau
    if results.count>0 {

        // Parfois iTunes retourne une collection, pas un morceau, alors nous vérifions 'nom' pour les deux cas de figure
        for result in results {

            var nom = result["trackName"] as? String
            if nom == nil {
                nom = result["collectionName"] as? String
            }

            // Parfois le prix est dans formattedPrice, d'autres fois dans collectionPrice... Et il arrive que ce soit un nombre flottant plutôt qu'une chaîne. C'est formidable!
            var prix = result["formattedPrice"] as? String
            if prix == nil {
                prix = result["collectionPrice"] as? String
                if price == nil {
                    var prixFloat: Float? = result["collectionPrice"] as? Float
                    var nf: NSNumberFormatter = NSNumberFormatter()
                    nf.maximumFractionDigits = 2
                    if prixFloat != nil {
                        prix = "$\(nf.stringFromNumber(prixFloat!)!)"
                    }
                }
            }

            let miniatureURL = result["artworkUrl60"] as? String ?? ""
            let imageURL = result["artworkUrl100"] as? String ?? ""
            let artisteURL = result["artistViewUrl"] as? String ?? ""

            var itemURL = result["collectionViewUrl"] as? String
            if itemURL == nil {
                itemURL = result["trackViewUrl"] as? String
            }

            var newAlbum = Album(nom: nom!, prix: prix!, miniatureImageURL: miniatureURL, largeImageURL: imageURL, itemURL: itemURL!, artisteURL: artisteURL)
            albums.append(newAlbum)
        }
    }
    return albums
}

Je sais que ça ressemble à un tas de code nouveau, mais en réalité il n'y a pas grand-chose qui se passe ici. Ça ne fait qu'itérer sur la liste issue de results, récupérer les valeurs de quelques champs, et insérer des valeurs par défaut si elles sont absentes.

L'opérateur ?? utilisé ici est assez cool. Il fonctionne de cette façon :

 
Sélectionnez
1.
let variableFinale = variableQuiPeutEtreNil ?? "Variable Qui N'est Sûrement Pas Nil"

La valeur de variableFinale est initialisée à variableQuiPeutEtreNil si elle ne vaut pas nil. Mais sinon ? Elle utilise alors la valeur de ce qui suit l'opérateur ??. Dans le cas présent, c'est la chaîne "Variable Qui N'est Sûrement Pas Nil".

Nous l'utilisons ici pour empêcher de passer des valeurs nil dans notre Album.

À la ligne 39, nous créons un objet Album. À la ligne 40 l'album est ajouté à la liste. Finalement, en ligne 43 la liste des albums est retournée de la fonction.

Si vous démarrez votre app maintenant, vous devriez voir une nouvelle liste d'albums apparaître. Sympa, pas vrai ?

VI. Créer une seconde vue

Pour vraiment afficher les détails d'un album, il nous faudra une nouvelle vue. Créons d'abord la classe. Ajoutez un fichier appelé DetailsViewController.swift qui hérite de UIViewController.

Notre contrôleur de vue sera assez simple au départ. Nous allons juste ajouter un album, et implémenter la méthode init de UIViewController ainsi que viewDidLoad().

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
import UIKit
 
class DetailsViewController: UIViewController {
     
    var album: Album?
     
    required init(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
    }
     
    override func viewDidLoad() {
        super.viewDidLoad()
    }
}

Ce code ne fait pas grand-chose pour le moment, mais ce n'est pas un problème. Nous avons juste besoin que la classe existe pour configurer notre storyboard.

Puisque nous allons mettre et enlever des vues sur la pile, nous voulons aussi avoir une barre de navigation. C'est difficile à expliquer en toutes lettres, et il y a un accord de confidentialité qui m'empêche de montrer certaines parties de Xcode 6 en images, alors j'ai créé une vidéo qui montre comment faire ça dans Xcode 5. Le procédé est pratiquement le même que pour Xcode 6 Bêta, et il n'est pas protégé par un quelconque accord de confidentialité.


Cliquez pour lire la vidéo


Dans la vidéo nous avons montré ceci :

  1. Intégrer notre contrôleur de vue dans un contrôleur de navigation par le biais du menu Editor de Xcode, en cliquant sur le « View Controller » puis en sélectionnant Editor → Embed In → Navigation Controller ;
  2. Ajouter un nouveau contrôleur de vue ;
  3. Changer sa classe et son identifiant de storyboard en DetailsViewController ;
  4. Contrôle-clic et faire glisser combo depuis la cellule du Table View dans le premier contrôleur de vue vers le nouveau contrôleur de vue que nous venons de créer, et sélectionner « show » comme type de transition.

Ce que fait cette dernière action, c'est créer une transition dans notre contrôleur de navigation qui place la nouvelle vue sur le haut de la pile. Si vous lancez l'app maintenant et cliquez sur une cellule, vous devriez voir l'animation qui affiche cette nouvelle vue.

Bâtissons une interface simple pour cette nouvelle vue. Elle contiendra une vue UIImageView de 100 par 100 pixels, et un Label de titre. Glissez ces objets depuis la bibliothèque d'objets et arrangez-les comme il vous plaît sur la nouvelle vue.

VII. Fournir les informations d'album à la nouvelle vue

Quand la transition du storyboard démarre, elle commence par appeler une fonction du contrôleur de vue actuellement à l'écran, nommée prepareForSegue. Nous allons intercepter cet appel pour pouvoir dire à notre nouveau contrôleur de vue quel est l'album qui nous intéresse. Ajoutez le code suivant dans SearchResultsViewController :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
    if let detailsViewController: DetailsViewController = segue.destinationViewController as? DetailsViewController {
        var albumIndex = appsTableView!.indexPathForSelectedRow()!.row
        var selectedAlbum = self.albums[albumIndex]
        detailsViewController.album = selectedAlbum
    }
}

Ce qui se passe ici, c'est que le paramètre de transition passé à la fonction a un membre appelé destinationViewController, qui est ce fameux contrôleur de vue DetailsViewController que nous venons de créer. Pour lui modifier son membre album, il faut d'abord le convertir en type DetailsViewController à l'aide du mot-clef as comme illustré ci-dessus.

Puis, en utilisant la méthode indexPathForSelectedRow() de notre Table View, nous pouvons déterminer quel album est sélectionné quand l'animation se déclenche. Avec cette information, nous disons à notre DetailsViewController quel album a été cliqué avant son affichage.

Là je vais vous montrer une jolie petite fonctionnalité de Xcode. Nous allons lui faire écrire du code pour nous.

Ouvrez à nouveau votre storyboard et commençons à créer des IBOutlets pour nos image, label, bouton et vue de texte. En haut à droite de l'interface de Xcode il y a un bouton « assistant ». L'icône ressemble à un nœud papillon et costume. La cliquer ouvre une fenêtre de code juste à côté de votre fenêtre du storyboard. Assurez-vous que l'un des panneaux affiche DetailsViewController.swift et l'autre affiche Main.storyboard.

Maintenant, pressez « contrôle » et faites glisser d'un clic votre image vers votre fenêtre de code. Visez juste une ligne en dessous de votre définition de classe pour DetailsViewController. Il va vous demander d'entrer un nom, alors appelez-la couvertureAlbum. Les options par défaut devraient aller bien ici. Après cela, vous devriez voir cette nouvelle ligne ajoutée au code :

 
Sélectionnez
1.
@IBOutlet weak var couvertureAlbum: UIImageView!

Nous venons de créer un nouvel IBOutlet, et maintenant il est connecté à notre contrôleur de vue DetailsViewController du storyboard. Qu'est-ce que vous en dites ?

Faites de même pour le label que vous avez ajouté à votre vue et appelez-le etiquetteTitre.

Ensuite, modifions la méthode viewDidLoad pour qu'elle charge l'information que nous faisons passer à nos objets de vue, voici le code final de DetailsViewController :

 
Sélectionnez
1.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
import UIKit

class DetailsViewController: UIViewController {
    var album: Album?
    @IBOutlet weak var etiquetteTitre: UILabel!
    @IBOutlet weak var couvertureAlbum: UIImageView!
    
    required init(coder aDecoder: NSCoder!) {
        super.init(coder: aDecoder)
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        etiquetteTitre.text = self.album?.titre
        couvertureAlbum.image = UIImage(data: NSData(contentsOfURL: NSURL(string: self.album!.largeImageURL)!)!)
    }
}

Les @IBOutlets sont les connexions avec l'interface utilisateur faites par notre storyboard, et notre méthode viewDidLoad insère le titre et la couverture de l'album à charger de notre objet Album.

Il est temps de lancer notre application et voir le résultat. Nous pouvons maintenant naviguer jusqu'aux détails des albums et obtenir une superbe vue grand format de la couverture de l'album et son titre. Comme nous avons empilé la vue dans un contrôleur de navigation, nous avons aussi un bouton de retour qui marche, gratis !

Si vous êtes parvenus jusqu'ici, j'aimerais vous féliciter personnellement alors faites-moi savoir sur Twitter que vous y êtes arrivés ! Vous êtes en bon chemin pour créer de véritables applications iOS avec Swift.

J'ai décidé d'étendre et raffiner cette série de tutoriels, ainsi que d'autres tutoriels et discussions sur l'utilisation de Swift, de Xcode et les services d'Apple dans un nouveau livre qui est en précommande.

VIII. Code source

Le source code complet de cette section est disponible ici.

Dans la partie 7, nous mettons en place une vue de détail avec un lecteur fonctionnel, et nous ajoutons des animations superbes.

IX. Vous avez une question ou un problème ?

Rejoignez-nous sur nos forums.

X. Remerciements Developpez

Nous remercions Jameson Quave de nous avoir aimablement autorisés à publier son article, dont le texte original peut être trouvé sur jamesonquave.comhttp://jamesonquave.com/blog/developing-ios-8-apps-using-swift-interaction-with-multiple-views/. Nous remercions aussi bredelet pour sa traduction, LeBzul pour sa relecture technique ainsi que ClaudeLELOUP pour sa relecture orthographique et Lana Bauer pour sa gabarisation.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Copyright © 2014 Jameson Quave - Developpez.com. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.