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.
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.
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 :
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 :
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 :
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.
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.
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 :
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 :
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 :
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 :
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.
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.
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 :
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().
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 :
- 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 ;
- Ajouter un nouveau contrôleur de vue ;
- Changer sa classe et son identifiant de storyboard en DetailsViewController ;
- 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 :
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 :
@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 :
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.
Retrouvez toute la série « Développer des Apps iOS 8 avec Swift »