Aller au contenu


iPhone SDK: UITableViewController et téléchargements d’images asynchrones

uitableviewcontroller_images1Dans des applications telles que Youtube ou même l’Appstore, il est fréquent de voir des UITableViewController affichant des images thumbnails. Ces images sont chargées de manière asynchrone, afin de ne pas bloquer l’UI pendant le chargement, lorsque vous scrollez dans la table. Typiquement, pour arriver à ce résultat, il faut créer un nouveau thread (NSThread ou NSOperation/NSOperationQueue) pour chaque téléchargement d’image, et renvoyer le résultat au main thread une fois terminé, pour procéder à l’affichage.

Dans un récent article, Mark Johnson propose une solution, simple et efficace, qui évite de devoir créer et gérer les threads manuellement (ce qui peut devenir complexe assez rapidement).  Elle se base sur l’objet NSURLConnection, permettant de charger le contenu d’une URL, de manière asynchrone. L’image téléchargée est ajoutée comme subView de notre UITableViewCell, au lieu d’utiliser la propriété image de la cellule.

L’idée proposée par Mark est basée sur une subclass de UIView.  Je vous propose ici ma version, reprenant son idée, mais se basant sur un subclassing de UIImageView, encore plus simple à mettre en place.

AsyncUIImageView.h

1
2
3
4
5
6
7
8
9
10
#import <Foundation/Foundation.h>
 
@interface AsyncUIImageView : UIImageView {
    NSURLConnection* connection;
    NSMutableData* data;
}
 
- (void)loadImageFromURL:(NSURL*)url;
 
@end

AsyncUIImageView.m

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
45
#import "AsyncUIImageView.h"
 
@implementation AsyncUIImageView
 
- (void)loadImageFromURL:(NSURL*)url {
    if (connection != nil) { 
	[connection release]; 
    }
    if (data != nil) { 
	[data release]; 
    }
    NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
    connection = [[NSURLConnection alloc] initWithRequest:request delegate:self];
    if (connection) {
  	data = [[NSMutableData data] retain];
    }
    else {
	//TODO error handling.
    }
}
 
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
    [data appendData:incrementalData];
}
 
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {
    [connection release];
    connection = nil;
 
    self.image = [UIImage imageWithData:data];
    self.contentMode = UIViewContentModeScaleAspectFit;
    self.autoresizingMask = ( UIViewAutoresizingFlexibleWidth || UIViewAutoresizingFlexibleHeight );
    [self setNeedsLayout];
    [data release];
    data = nil;
}
 
- (void)dealloc {
    [connection cancel];
    [connection release];
    [data release];
    [super dealloc];
}
 
@end

Dans votre UITableViewController

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithFrame:CGRectZero reuseIdentifier:CellIdentifier] autorelease];
    } 
    else {
	AsyncUIImageView* oldImage = (AsyncUIImageView*)[cell.contentView viewWithTag:999];
	[oldImage removeFromSuperview];
    }
 
    AsyncUIImageView* asyncImage = [[[AsyncUIImageView alloc] initWithFrame:CGRectMake(0,0,75,75)] autorelease];
    asyncImage.tag = 999;
    NSURL* url = [[NSURL alloc] initWithString: [myImagesArray objectAtIndex:indexPath.row]];
    [asyncImage loadImageFromURL:url];
 
    [cell.contentView addSubview:asyncImage];
 
    return cell;
}

Pour parfaire cette solution, il reste à:
– mettre en place un système de cache des images téléchargées
– gérer les éventuels problèmes de connexion dans notre AsyncUIImageView

Thanks Mark!

Posté dans développement, iPhone. Tags , , .

5 commentaire(s)

  1. Thanks Mark!

    You have done great job!

  2. If you’d like to thank Mark, the original author of this idea, you can thank him directly on his blog 😉
    http://www.markj.net/iphone-asynchronous-table-image/

  3. Sisi87 dit

    Merci pour ce tuto mais je n’arrive pas à le faire fonctionner, j’ai plusieurs erreur (Peut tu m’expliquer comment créer « myImagesArray »)

  4. bkill dit

    myImagesArray est simplement un tableau (NSArray ou NSMutableArray) contenant les URL des images pour chaque row de ta tableView, que tu pourras par exemple créer dans ta méthode loadView.

  5. Vincent dit

    Bonjour,

    Je suis actuellement en train d’essayer d’implémenter cette méthode mais je rencontre quelques difficultés techniques et/ou de compréhension.

    Je ne souhaite pas afficher mes images dans une TableView mais simplement dans une vue, dans les UIImageView que j’ai créé auparavent.

    Tout semble fonctionner mais les images ne s’affichent pas et aucune erreur n’est renvoyée.
    Je me suis rendu compte que la méthode loadImageFromURL est bien appelée, que la connection est bien créée et != nil mais didReceivedResponse et didReceivedData ne sont jamais appelées.

    Avez vous une idée du pourquoi ?
    Par ailleurs, quel est la signification et l’utilité du « tag » ?

Quelques tags HTML sont acceptés

(obligatoire)

(obligatoire, mais ne sera pas diffusé)

ou faire un rétrolien depuis votre site.