Salve a tutti, dopo una lunghissima pausa torno a scrivere qualche mini tutorial, spero abbiate seguito i miei articoli su piattaforma Bada nel frattempo! Oggi vorrei proporvi la risoluzione di un annoso problema riguardante il caricamento asyncrono di immagini all’interno delle tabelle.
Come sicuramente saprete, è relativamente semplice in Xcode visualizzare un’immagine prendendo il link direttamente via HTTP, ma il caricamente delle righe delle table richiede che vengano scaricate le immagini che visualizziamo PRIMA di visualizzare la riga stessa. L’obiettivo che volevo raggiungere era lo stesso che potete vedere sfogliando l’applicazione ‘App Store’ nella sezione categoria su iPhone/iPod/iPad… in cui se ci fate caso, vedrete apparire immediatamente i testi e poco dopo le 5 icone che vengono scaricate in background asincronicamente (gran parola!).
Innanzitutto creaiamo la classe “AsyncImageView” che richiameremo dal nosto metodo “cellForRowAtIndexPath”.
Di seguito il file AsyncImageView.h
#import
@interface AsyncImageView : UIView {
//could instead be a subclass of UIImageView instead of UIView, depending on what other features you want to
// to build into this class?
NSURLConnection* connection; //keep a reference to the connection so we can cancel download in dealloc
NSMutableData* data; //keep reference to the data so we can collect it as it downloads
//but where is the UIImage reference? We keep it in self.subviews - no need to re-code what we have in the parent class
}
- (void)loadImageFromURL:(NSURL*)url;
- (UIImage*) image;
@end
Ed il relativo file di implementazione:
#import "AsyncImageView.h"
@implementation AsyncImageView
- (void)dealloc {
[connection cancel]; //in case the URL is still downloading
[connection release];
[data release];
[super dealloc];
}
- (void)loadImageFromURL:(NSURL*)url {
if (connection!=nil) { [connection release]; } //in case we are downloading a 2nd image
if (data!=nil) { [data release]; }
NSURLRequest* request = [NSURLRequest requestWithURL:url cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:60.0];
connection = [[NSURLConnection alloc] initWithRequest:request delegate:self]; //notice how delegate set to self object
}
//the URL connection calls this repeatedly as data arrives
- (void)connection:(NSURLConnection *)theConnection didReceiveData:(NSData *)incrementalData {
if (data==nil) { data = [[NSMutableData alloc] initWithCapacity:2048]; }
[data appendData:incrementalData];
}
//the URL connection calls this once all the data has downloaded
- (void)connectionDidFinishLoading:(NSURLConnection*)theConnection {
//so self data now has the complete image
[connection release];
connection=nil;
if ([[self subviews] count]>0) {
//then this must be another image, the old one is still in subviews
[[[self subviews] objectAtIndex:0] removeFromSuperview]; //so remove it (releases it also)
}
//make an image view for the image
UIImageView* imageView = [[[UIImageView alloc] initWithImage:[UIImage imageWithData:data]] autorelease];
//make sizing choices based on your needs, experiment with these. maybe not all the calls below are needed.
imageView.contentMode = UIViewContentModeScaleAspectFit;
imageView.autoresizingMask = ( UIViewAutoresizingFlexibleWidth || UIViewAutoresizingFlexibleHeight );
[self addSubview:imageView];
imageView.frame = self.bounds;
[imageView setNeedsLayout];
[self setNeedsLayout];
[data release]; //don't need this any more, its in the UIImageView now
data=nil;
}
- (UIImage*) image {
UIImageView* iv = [[self subviews] objectAtIndex:0];
return [iv image];
}
@end
Una volta che abbiamo la classe pronta, basterà copiarla dentro a qualsiasi progetto per poterla utilizzare, vediamo come.
Vi riporto per completezza tutto il metodo “cellForRowAtIndexPath”, che come saprete si occupa di valorizzare la riga che stiamo caricando in quel momento.
- (UITableViewCell *)tableView:(UITableView *)tableView
cellForRowAtIndexPath:(NSIndexPath *)indexPath {
static NSString *CellIdentifier = @"ImageCell";
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
if (cell == nil) {
[[NSBundle mainBundle] loadNibNamed:@"CellaRiepilogo" owner:self options:NULL];
cell = nibLoadedCell;
} else {
AsyncImageView* oldImage = (AsyncImageView*)
[cell.contentView viewWithTag:999];
[oldImage removeFromSuperview];
}
CGRect frame;
frame.size.width=47; frame.size.height=47;
frame.origin.x=8; frame.origin.y=8;
AsyncImageView* asyncImage = [[[AsyncImageView alloc]
initWithFrame:frame] autorelease];
UIImage *myImage = [UIImage imageNamed:@"blackaccessorybutton.png"];
UIImageView *imageView = [[UIImageView alloc] initWithImage:myImage];
[cell setAccessoryView:imageView];
Sciopero *sciopero = [dati objectAtIndex:indexPath.row];
UILabel *dataLabel = (UILabel *)[cell viewWithTag:1];
UILabel *settoreLabel= (UILabel *)[cell viewWithTag:2];
UILabel *modalitaLabel= (UILabel *)[cell viewWithTag:3];
UILabel *descrizioneLabel= (UILabel *)[cell viewWithTag:4];
dataLabel.text = [NSString stringWithFormat:@"%@", [sciopero.data uppercaseString]];
settoreLabel.text = [NSString stringWithFormat:@"%@", sciopero.settore];
modalitaLabel.text = [NSString stringWithFormat:@"%@", [sciopero.modalita lowercaseString]];
descrizioneLabel.text = [NSString stringWithFormat:@"%@", [sciopero.descrizione lowercaseString]];
asyncImage.tag = 999;
NSURL *url = [NSURL URLWithString: sciopero.icona];
//UIImage *img = [UIImage imageWithData:[NSData dataWithContentsOfURL:url]];
//categoria.immagine = img;
[asyncImage loadImageFromURL:url];
[cell.contentView addSubview:asyncImage];
return cell;
}
Nel mio caso utilizzo una cella personalizzata con uno XIB associato di nome “CellaRiepilogo.xib”.
La parte da personalizzare a seconda dei casi è la seguente:
CGRect frame;
frame.size.width=47; frame.size.height=47;
frame.origin.x=8; frame.origin.y=8;
AsyncImageView* asyncImage = [[[AsyncImageView alloc]
initWithFrame:frame] autorelease];
Ossia, creiamo il frame delle dimensioni che ci interessano (nel mio caso 47×47) e decidiamo dove farlo apparire (in questo caso coordinate x=8 e y=8).
Successivamente:
asyncImage.tag = 999;
NSURL *url = [NSURL URLWithString: sciopero.icona];
[asyncImage loadImageFromURL:url];
[cell.contentView addSubview:asyncImage];
E il gioco é fatto!
Ricordatevi di mettere l’import:
#import "AsyncImageView.h"
Non ho scritto io quella classe, e purtroppo non ricordo dove l’ho pescata, peró ne ho trovate diverse , e questa l’ho personalizzata in diverse parti.
Questo stesso codice l’ho usato giá in parecchie applicazioni:
- iQuimall, mi serviva per caricare le cover/skin degli iphone via web
- iScioperi Trasporti – dovevo caricare tutti i dati comprensive di immagini da un mio XML su web
(questo spezzone di codice viene proprio da lí!)
- Tutte le altre mie app, che hanno nel tab bar l’opzione “Altre App” appare la lista completa delle
applicazioni da me realizzate, e cliccando sulla riga si va direttamente sull’AppStore per l’acquisto!
Spero vi sia utile!









7 Responses to “T#067 – Caricare immagini nelle tabelle in modalità asincrona”
30 Luglio 2010
andreaAppCodema se usiamo dei thread possiamo ottenere gli stessi risultati o sbaglio?
31 Luglio 2010
Tweets that mention Caricare immagini nelle tabelle in modalità asincrona [iPhone SDK] | devAPP -- Topsy.com[…] This post was mentioned on Twitter by David Funaro, devAPP. devAPP said: Caricare immagini nelle tabelle in modalità asincrona http://bit.ly/cBorop […]
11 Agosto 2010
LeleCiao eccomi di nuovo qui a leggervi dopo essere tornato dalle ferie, questo articolo mi e’ utile molto, difatti stavo scrivendo una semplice app che scarica gli articolida un feed rss e le img caricate insieme alla cella rallentano di molto lo scrolling della view 🙂 ora provo, grazie in anticipo
11 Agosto 2010
LeleHo provato la classe per il caricamneto asincrono delle img, ora ho risolto il mio problema, lo scrolling dei feed rss e’ rapido e fluido con le img che vengono caricate successivamente, grazie mille a presto 🙂
26 Agosto 2010
Francesco BurelliOttimo tutorial, davvero interessante, ma vorrei aggiungere delle informazioni: se avessimo una tabella di tanti elementi (troppi) scrollando in fretta tutta la tabella si andrebbero a creare tantissime richieste per scaricare le icone e tantissimi “cancel” inutilmente, infatti se notiamo attentamente l’applicazione “App Store” vediamo che finché non stacchiamo il dito dallo schermo o comunque finché lo scrolling non si ferma “App Store” non carica le icone. Se vi posizionate all’inizio della tabella e scollate senza fermarvi fino in fondo, noterete che le icone caricate saranno solo quelle all’inizio e alla fine della tabella poiché l’app carica le icone solo quando la tabella è ferma.
Quindi secondo me andrebbe fatto qualcosa di diverso utilizzando magari qualche metodo del delegato della tabella: quando la tabella smette di scorrere allora scarico le icone dal link per esempio.
14 Ottobre 2010
Fabiomi associo a questa richiesta di tutorial.
grazie mille!
18 Marzo 2011
DavideComplimenti. E’ possibile utilizzare la stessa classe per scaricare in modo asincronon una pagina web anziche una immagine?
Grazie