I. Ce tableau est lent ! Accélérons-le !▲
Nous avons la fonctionnalité que nous cherchions, mais si vous l'exécutez, vous allez constater que c'est très lent ! Le problème est que les images des cellules sont téléchargées une à une à partir du thread de l'interface et elles ne sont pas mises en cache. Améliorons cela.
Commençons par ajouter un dictionnaire comme membre de notre classe SearchResultsViewController :
var
imageCache
=
[
String
:
UIImage
]()
II. Syntaxe des dictionnaires▲
C'est la première fois que nous voyons cette syntaxe, laissez-moi donc vous donner une explication rapide.
Le type spécifié ici est [
String
:
UIImage
]
, qui est similaire au type NSDictionary d'Objective-C, mais qui est plus strict sur le type. Il prend comme clef un String et stocke un UIImage comme valeur.
Donc, si j'ai une image nommée Bob, liée à l'UIImage avec le nom de fichier Bob.jpg, je vais l'ajouter au dictionnaire comme ceci :
imageCache
[
"Bob"
]
=
UIImage
(
named
:
"Bob.jpg"
)
L'instruction UIImage
(
named
:
"Bob.jpg"
)
instancie un UIImage à partir du fichier nommé Bob.jpg : c'est la syntaxe standard en Swift pour les fichiers locaux. Le dictionnaire utilise un sous-script pour définir ou récupérer ses valeurs. Donc, si je veux obtenir l'image de Bob, je peux simplement utiliser :
let
imageOfBob
=
imageCache
[
"Bob"
]
On ajoute les parenthèses pour appeler le constructeur qui initialise le dictionnaire vide. Tout simplement, comme nous utilisons APIController(), nous avons besoin d'utiliser [
String
:
UIImage
]()
. (NDT On parle ici des parenthèses qui différencient le type de l'appel du constructeur.)
Nous désirons améliorer quelques points de notre méthode cellForRowAtIndexPath, notamment, nous voulons accéder aux images à partir de notre cache ou les télécharger en tâche de fond si elles n'existent pas. À la suite de chaque téléchargement, les images sont stockées en cache afin que nous puissions y accéder au besoin, et ce, sans avoir besoin de relancer un processus de téléchargement.
Commençons par déplacer l'appel de imgData en dehors de notre liaison optionnelle, vers l'intérieur du bloc qui met à jour les cellules. Nous faisons cela afin de pouvoir utiliser les données image à partir de notre cache ou de les télécharger, selon le cas. Ici, nous allons passer à l'utilisation de la méthode sendAsynchronousRequest de NSURLConnection, afin de télécharger l'image dans un thread d'arrière-plan.
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.
45.
46.
47.
48.
49.
50.
51.
func
tableView
(
tableView
:
UITableView
,
cellForRowAtIndexPath indexPath
:
NSIndexPath
)
->
UITableViewCell
{
let
cell
:
UITableViewCell
=
tableView
.
dequeueReusableCellWithIdentifier
(
kCellIdentifier
)
as
!
UITableViewCell
if
let
rowData
:
NSDictionary
=
self
.
tableData
[
indexPath
.
row
]
as
?
NSDictionary
,
// Prenez la clé artworkUrl60 pour obtenir l'URL d'une image pour la miniature de l'application
urlString
=
rowData
[
"artworkUrl60"
]
as
?
String
,
imgURL
=
NSURL
(
string
:
urlString
),
// Obtenez le prix formaté en chaîne, pour l'afficher dans le sous-titre
prixFormate
=
rowData
[
"formattedPrice"
]
as
?
String
,
// Obtenez le title du morceau
nomMorceau
=
rowData
[
"trackName"
]
as
?
String
{
// Affichez le prix formaté dans le sous-titre
cell
.
detailTextLabel
?.
text
=
prixFormate
// Mettez à jour le texte de textLabel pour afficher le nom du morceau obtenu de l'API
cell
.
textLabel
?.
text
=
nomMorceau
// 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"
)
// Si cette image se trouve déjà en cache, ne plus la recharger
if
let
img
=
imageCache
[
urlString
]
{
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
:
imgURL
)
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
[
urlString
]
=
image
// Mettre à jour la cellule
dispatch_async
(
dispatch_get_main_queue
(),
{
if
let
cellToUpdate
=
tableView
.
cellForRowAtIndexPath
(
indexPath
)
{
cellToUpdate
.
imageView
?.
image
=
image
}
})
}
else
{
println
(
"Erreur:
\(
error
.
localizedDescription
)
"
)
}
})
}
}
return
cell
}
Donc, que veut dire ce code ? Essayons de comprendre les changements…
IMPORTANT !
Avant de télécharger l'image réelle, assurons-nous de configurer une image par défaut pour la cellule. Cela est nécessaire si vous voulez insérer une vue d'image. Dans le cas contraire, les images chargées ne s'afficheront pas. Créez une image vide (ici, j'utilise une taille de 52 par 52 pixels, mais cela importe peu) et importez-la dans votre projet en faisant glisser le fichier pris à partir du finder vers votre projet dans Xcode, nommez-la « Blank52 » et indiquez à votre cellule d'utiliser cette image. Vous pouvez simplement prendre mon fichier image à cette adressehttp://jamesonquave.com/tutImg/Blank52.png (par un clic droit et « Enregistrez sous »...)
cell
.
imageView
?.
image
=
UIImage
(
named
:
"Blank52"
)
Maintenant, votre application devrait être moins sujette aux plantages et affichera toujours une cellule contenant une image.
III. Effectuer les téléchargements dans un thread en arrière-plan▲
Commençons par vérifier notre cache d'images afin de chercher si l'image est déjà téléchargée. Nous utilisons la liaison optionnelle pour vérifier la présence de l'image en cache.
25.
26.
if
let
img
=
imageCache
[
urlString
]
{
cell
.
imageView
?.
image
=
img
}
Si l'image n'existe pas (et initialement c'est le cas), nous devons la télécharger. Il y a plusieurs façons de débuter un téléchargement. Précédemment, nous utilisions la méthode dataWithContentsOfFile de la classe NSData, mais ici, nous allons utiliser la méthode sendAsynchronousRequest de la classe NSURLConnection qui est plus proche du fonctionnement de notre API. La principale raison est que nous voulons exécuter plusieurs petites requêtes rapidement, et nous voulons le faire en arrière-plan.
Regardez la ligne de l'appel à la méthode statique sendAsynchronousRequest de NSURLConnection, qui prend une fonction/fermeture comme paramètre de completionHandler. Les lignes après cet appel représentent une fonction exécutée après le retour de la requête asynchrone.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
42.
43.
44.
45.
46.
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
[
urlString
]
=
image
// Mettre à jour la cellule
dispatch_async
(
dispatch_get_main_queue
(),
{
if
let
cellToUpdate
=
tableView
.
cellForRowAtIndexPath
(
indexPath
)
{
cellToUpdate
.
imageView
?.
image
=
image
}
})
}
else
{
println
(
"Erreur:
\(
error
.
localizedDescription
)
"
)
}
})
À l'intérieur du bloc, nous recevons en retour quelques variables : response, data et error.
S'il n'existe aucune erreur, nous créons une instance d'UIImage à partir des données reçues, en utilisant le constructeur UIImage(data: data).
Puis, nous stockons notre image dans le cache d'images en utilisant son URL comme clef. Cela veut dire que nous pouvons rechercher l'image dans notre dictionnaire à partir de cette dernière à n'importe quel moment et dans n'importe quel contexte.
self
.
imageCache
[
urlString
]
=
image
Ensuite, nous affectons l'image à la cellule à partir du thread qui gère l'interface (file principale).
40.
41.
42.
43.
dispatch_async
(
dispatch_get_main_queue
(),
{
if
let
cellToUpdate
=
tableView
.
cellForRowAtIndexPath
(
indexPath
)
{
cellToUpdate
.
imageView
?.
image
=
image
}
})
Vous remarquez que nous utilisons également ici la méthode cellForRowAtIndexPath(). Nous le faisons parce que parfois, la cellule que ce code affichait n'est plus visible et a été réutilisée. Donc, pour éviter de placer l'image sur la mauvaise cellule, nous l'identifions dans la tableView sur base du chemin de l'indice. Si celui-ci est nil, alors la cellule n'est plus visible et nous ne devons plus la mettre à jour.
Maintenant, essayons d'exécuter notre application pour observer notre implantation rapide et souple du tableView.
IV. Code source▲
Le code complet est disponible sur le dépôt Github dans la branche « Part5 ».
La partie 6 se concentre sur l'ajout d'un nouveau contrôleur de vues que nous pouvons afficher et qui charge des données provenant d'iTunes.
V. Vous avez une question ou un problème ?▲
Rejoignez-nous sur nos forums.
VI. 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-apps-using-swift-part-5-async-image-loading-and-caching/. Nous remercions aussi Sirus64 pour sa traduction, LeBzul pour sa relecture technique ainsi que jacques_jean et ClaudeLELOUP pour leur relecture orthographique et Winjerome pour sa gabarisation.
Aller à la partie 6 maintenant ->Partie 6 : Interaction avec des vues multiples
Retrouvez toute la série « Développer des Apps iOS 8 avec Swift »