Développer des Apps iOS 8 avec Swift

Partie 6 : Interaction avec des vues multiples

Jameson Quave

Traducteur

Bredelet

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 1.

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. Commentez .

Retrouvez toute la série « Développer des Apps iOS 8 avec Swift »

Aller à la partie 5 maintenant ->

- Titre : Développer des Apps iOS 8 avec Swift

- Auteur : Jameson Quave

- Traducteur : Bredelet

- Parution : 29 juillet 2015

- Mise à jour : 22 février 2016

- Public visé :

- Licence : 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.

Voir également :

- Partie 1 : introduction - application basique Hello World

- Partie 2 : affichage des résultats d'une requête

- Partie 3 : les bonnes pratiques

- Partie 4 : ajouter les interactions

- Partie 5 : chargement asynchrone d'images et mise en cache

- Partie 7 : animations, audio et cellules Table View personnalisées

- Apprendre Swift à partir d'Objective-C

- Comprendre les optionnelles : quand et comment les utiliser ?

Sommaire

- I. Modification de l'API Controller

- II. Recherche d'albums

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

- IV. Utiliser notre nouveau modèle d'album

- V. Créer des objets Album depuis JSON

- VI. Créer une seconde vue

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

- VIII. Code source

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

- X. Remerciements Developpez

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.

1. init(delegate: APIControllerProtocol) {

2. self.delegate = delegate

3. }

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.

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 :

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 :

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 :

1. override func viewDidLoad() {

2. super.viewDidLoad()

3. api = APIController(delegate: self)

4. api.searchItunesFor("JQ Software")

5. }

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.

1. override func viewDidLoad() {

2. super.viewDidLoad()

3. api = APIController(delegate: self)

4. UIApplication.sharedApplication().networkActivityIndicatorVisible = true

5. api.searchItunesFor("Beatles")

6. }

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

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 :

1. import Foundation

3. struct Album {

4. let titre: String

5. let prix: String

6. var miniatureImageURL: String

7. var largeImageURL: String

8. var itemURL: String

9. var artisteURL: String

11. init(nom: String, prix: String, miniatureImageURL: String, largeImageURL: String, itemURL: String, artisteURL: String) {

12. self.titre = nom

13. self.prix = prix

14. self.miniatureImageURL = miniatureImageURL

15. self.largeImageURL = largeImageURL

16. self.itemURL = itemURL

17. self.artisteURL = artisteURL

18. }

19. }

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 :

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 :

1. func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {

2. return albums.count

3. }

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

1. func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {

2. let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as! UITableViewCell

3. let album = self.albums[indexPath.row]

5. // Obtenez le prix formaté en chaîne, pour l'afficher dans le sous-titre

6. cell.detailTextLabel?.text = album.prix

7. // Mettez à jour le texte de textLabel pour utiliser le titre provenant du modèle de l'album

8. cell.detailTextLabel?.text = album.titre

9. // Commencez par configurer l'image de la cellule à partir d'un fichier statique

10. // Sans cela, nous allons nous retrouver sans une prévisualisation de l'image !

11. cell.imageView?.image = UIImage(named: "Blank52")

12. let miniatureURLString = album.miniatureImageURL

13. let miniatureURL = NSURL(string: miniatureURLString)!

15. // Si cette image se trouve déjà en cache, ne plus la recharger

16. if let img = imageCache[miniatureURLString] {

17. cell.imageView?.image = img

18. }

19. else {

20. // L'image n'existe pas en cache, la télécharger

21. // Nous devrions faire cela dans un thread d'arrière-plan

22. let request: NSURLRequest = NSURLRequest(URL: miniatureURL)

23. let mainQueue = NSOperationQueue.mainQueue()

24. NSURLConnection.sendAsynchronousRequest(request, queue: mainQueue, completionHandler: { (response, data, error) -> Void in

25. if error == nil {

26. // Convertir les données téléchargées en un objet UIImage

27. let image = UIImage(data: data)

28. // Stocker l'image dans notre cache

29. self.imageCache[miniatureURLString] = image

30. // Mettre à jour la cellule

31. dispatch_async(dispatch_get_main_queue(), {

32. if let cellToUpdate = tableView.cellForRowAtIndexPath(indexPath) {

33. cellToUpdate.imageView?.image = image

34. }

35. })

36. }

37. else {

38. println("Error: \(error.localizedDescription)")

39. }

40. })

41. }

42. return cell

43. }

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.

1. func didReceiveAPIResults(results: NSArray) {

2. dispatch_async(dispatch_get_main_queue(), {

3. self.albums = Album.albumsWithJSON(results)

4. self.appsTableView!.reloadData()

5. UIApplication.sharedApplication().networkActivityIndicatorVisible = false

6. })

7. }

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.

1. static func albumsWithJSON(results: NSArray) -> [Album] {

2. // Créer un tableau d'albums vide, à remplir depuis cette liste

3. var albums = [Album]()

5. // Stocker les résultats dans notre tableau

6. if results.count>0 {

8. // Parfois iTunes retourne une collection, pas un morceau, alors nous vérifions 'nom' pour les deux cas de figure

9. for result in results {

11. var nom = result["trackName"] as? String

12. if nom == nil {

13. nom = result["collectionName"] as? String

14. }

16. // 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!

17. var prix = result["formattedPrice"] as? String

18. if prix == nil {

19. prix = result["collectionPrice"] as? String

20. if price == nil {

21. var prixFloat: Float? = result["collectionPrice"] as? Float

22. var nf: NSNumberFormatter = NSNumberFormatter()

23. nf.maximumFractionDigits = 2

24. if prixFloat != nil {

25. prix = "$\(nf.stringFromNumber(prixFloat!)!)"

26. }

27. }

28. }

30. let miniatureURL = result["artworkUrl60"] as? String ?? ""

31. let imageURL = result["artworkUrl100"] as? String ?? ""

32. let artisteURL = result["artistViewUrl"] as? String ?? ""

34. var itemURL = result["collectionViewUrl"] as? String

35. if itemURL == nil {

36. itemURL = result["trackViewUrl"] as? String

37. }

39. var newAlbum = Album(nom: nom!, prix: prix!, miniatureImageURL: miniatureURL, largeImageURL: imageURL, itemURL: itemURL!, artisteURL: artisteURL)

40. albums.append(newAlbum)

41. }

42. }

43. return albums

44. }

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 :

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().

1. import UIKit

3. class DetailsViewController: UIViewController {

5. var album: Album?

7. required init(coder aDecoder: NSCoder) {

8. super.init(coder: aDecoder)

9. }

11. override func viewDidLoad() {

12. super.viewDidLoad()

13. }

14. }

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é.

Animation en format "youtube" non supportée.
Contenu uniquement consultable en ligne à cette adresse :
https://www.youtube.com/watch?v=Vms2hZwg0Zk

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 :

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

2. if let detailsViewController: DetailsViewController = segue.destinationViewController as? DetailsViewController {

3. var albumIndex = appsTableView!.indexPathForSelectedRow()!.row

4. var selectedAlbum = self.albums[albumIndex]

5. detailsViewController.album = selectedAlbum

6. }

7. }

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 :

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 :

1. import UIKit

3. class DetailsViewController: UIViewController {

4. var album: Album?

5. @IBOutlet weak var etiquetteTitre: UILabel!

6. @IBOutlet weak var couvertureAlbum: UIImageView!

8. required init(coder aDecoder: NSCoder!) {

9. super.init(coder: aDecoder)

10. }

12. override func viewDidLoad() {

13. super.viewDidLoad()

14. etiquetteTitre.text = self.album?.titre

15. couvertureAlbum.image = UIImage(data: NSData(contentsOfURL: NSURL(string: self.album!.largeImageURL)!)!)

16. }

17. }

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.com. 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.

Retrouvez toute la série « Développer des Apps iOS 8 avec Swift »

Aller à la partie 7 maintenant ->