Ed eccoci arrivati all’ultima puntata di questo lungo tutorial, vedremo oggi come realizzare una semplice interfaccia grafica per il nostro programma.
L’idea è quella di realizzare un’unica schermata con quattro pulsanti, due per navigare avanti e indietro nell’elenco delle foto, un pulsante per salvare la foto su nostro telefono, e un pulsante per avviare il download del file xml da internet.
Aggiungeremo inoltre: una label per visualizzare alcune informazioni, un controllo UIActivityview da visualizzare durante le attese, un controllo UIImageView per visualizzare l’immagine, un array dove memorizzare gli URL delle foto e un intero che memorizzi l’indice della foto che stiamo visualizzando.
Un bel po’ di roba, no?
Apriamo quindi il nostro caro vecchio progetto, e modifichiamo il file devFlickrViewController.h inserendo queste dichiarazioni:
@interface devFlickrViewController : UIViewController {
UILabel *currentPhotoNum;
UIImageView *imageView;
NSArray *urlArray;
int currentphoto;
UIActivityIndicatorView *act;
}
@property (nonatomic,retain) IBOutlet UILabel *currentPhotoNum;
@property (nonatomic,retain) IBOutlet UIImageView *imageView;
@property (nonatomic,retain) IBOutlet UIActivityIndicatorView *act;
- (IBAction)nextPhoto;
- (IBAction)prevPhoto;
- (IBAction)savePhoto;
- (IBAction)refreshPhotoList;
Creiamo l’interfaccia tramite Interface Builder, quello che vedete in questa foto è il massimo che il mio estro artistico sia riuscito a partorire:
Non dimenticate di collegare correttamente gli IBOutlet e di scivere i @syntetize nel file .m (commentate se ci sono difficoltà con questa parte!)
Passiamo ora a scrivere il codice delle funzioni che abbiamo dichiarato, iniziamo da refreshPhotoList
- (IBAction)refreshPhotoList
{
[act startAnimating];
interestingness *inter = [[interestingness alloc] initWithApiKey:@"MY_APY_KEY_IS_PRIVATE"];
[inter downloadXML];
URLbuilder *urlBuilder = [[URLbuilder alloc] initWithXMLdata:[inter getinterestingnessXML]];
[urlBuilder startParsing];
urlArray = [[NSArray arrayWithArray:[urlBuilder getURLarray]] retain];
[inter release];
[urlBuilder release];
[act stopAnimating];
}
Esaminiamo questo codice: per prima cosa visualizziamo la “rotellina”, poi istanziamo un oggetto di tipo interestigness e invochiamo su di esso il metodo downloadXML per far si che l’oggetto recuperi l’intero file xml dal sito di flickr.
Successivamente istanziamo un oggetto di tipo urlBuilder passando come parametro proprio il file xml appena scaricato e invochiamo su di esso il metodo startParsing.
Ricordiamo che l’oggetto urlBuilder ha un metdo getURLarray che restituisce l’array con gli URL delle foto, invochiamo quindi tale metodo e memorizziamo questo array nella variabilie urlArray.
A questo punto possiamo effettuare il release degli oggetti inter e urlBuilder. (nessun tifo calcistico 🙂 )
Eseguendo l’applicazione notiamo una cosa poco gradita, cliccando sul pulsante che avvia questo metodo l’interfaccia si blocca per qualche secondo durante la fase di download del file xml che come abbiamo visto nella puntata precedente è una chiamata sincrona, inoltre l’activityview (la rotellina) non viene proprio visualizzata.
Risolveremo questo problema alla fine di questo tutorial, per il momento lasciamolo così.
Dopo aver cliccato sul pulsante refresh ci si aspetta che venga visualizzata la prima foto, creiamo quindi un metodo che chiameremo viewPhotoAtIndex:
- (IBAction)viewPhotoAtIndex:(int)position
{
NSURL *url = [NSURL URLWithString: [urlArray objectAtIndex:position]];
NSData *data = [NSData dataWithContentsOfURL:url];
UIImage *img = [[UIImage alloc] initWithData:data];
[imageView setImage:img];
currentphoto = position;
currentPhotoNum.text = [NSString stringWithFormat:@"Foto %d su %d",position+1,[urlArray count]];
[img release];
}
Il lavoro svolto da questo medoto dovrebbe essere chiaro, il metodo accetta un intero come parametro, preleva dall’array degli URL quello in tale posizione, utilizza il metodo dataWithContentsOfURL per effettuare il download della foto, e la visualizza.
Aggiorniamo il valore di currentphoto e visualizziamo nella label il testo “foto x su y”.
Ho utilizzato position+1 perchè all’utente il fatto che gli array siano indicizzati da 0 non interessa, e non sarebbe carino vedere la scritta “foto 0 su 100” per la prima foto e “foto 99 su 100” per l’ultima.
Aggiungiamo quindi al metodo precedente (refreshPhotoList), giusto prima della chiusura, la riga
[self viewPhotoAtIndex:0];
in modo da visualizare la prima foto.
Fatto questo implementare i due metodi nextPhoto e prevPhoto è molto semplice perchè sarà sufficiente scrivere:
- (IBAction)nextPhoto
{
if (currentphoto + 1 < [urlArray count]) {
[self viewPhotoAtIndex:currentphoto + 1];
}
}
- (IBAction)prevPhoto {
if (currentphoto - 1 >= 0 )
{
[self viewPhotoAtIndex:--currentphoto];
}
}
Un semplice controllo ci impedisce di andare “out of range” sul nostro array.
Non ci resta che implementare la funzione per salvare l’immagine su nostro cellulare:
Il comando sarebbe semplice, perché basterebbe questo codice:
- (IBAction)savePhoto {
UIImage *img = [imageView image];
UIImageWriteToSavedPhotosAlbum( img, self ,nil, nil );
}
Però in questo caso non potremmo verificare se il salvataggio è andato o meno a buon fine, allora sfruttiamo il terzo parametro di questa funzione è un selector, ovvero è il nome di un’altra funzione che verrà automaticamente invocata quando il metodo avrà finito di salvare l’immagine.
Questo avviene perchè il salvataggio è ASINCRONO ovvero parte quando invochiamo questo metodo, ma poi lavora “per conto suo”, invocherà il metodo specificato nel selector per informarci dell’esito dell’operazione.
Secondo le indicazioni delle apple il metodo deve essere di questa forma.
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo
{
NSString *message;
NSString *title;
if (!error)
{
title = @"Ok";
message = @"foto salvata";
}
else
{
title = @"Error";
message = [error description];
}
UIAlertView *alert = [[UIAlertView alloc]
initWithTitle:title
message:message
delegate:self
cancelButtonTitle:@"OK"
otherButtonTitles:nil];
;
;
}
a questo punto la funzione per il salvataggio delle foto diventa:
- (IBAction)savePhoto {
UIImage *img = [imageView image];
UIImageWriteToSavedPhotosAlbum( img, self, @selector(image:didFinishSavingWithError:contextInfo:), nil );
}
A questo punto il programma sarebbe pure finito, cerchiamo quindi di capire bene il problema del blocco in fase di download del file xml.
Il problema è che il download del file xml è quella che si chiama in gergo una “chiamata bloccante” ovvero una funzione o un metodo che impedisce la normale esecuzione del programma fin quando non è terminato.
Per evitare che queste funzioni blocchino quindi tutto il programma bisogna farle eseguire in una sorta di “sotto-programma”.
Questi sotto-programmi si chiamano thread e il foundation framework mette a disposizione degli oggetti per crearli e manipolarli comodamente.
Qui ci starebbe la frase di rito: “non me ne vogliano i più esperti per questa eccessiva esemplificazione”, frase scritta perlopiù non per scusarsi ma per non dar l’impressione di essere degli inesperti del settore.
La sintassi per eseguire una funzione bloccante su un thread separato è questa:
[NSThread detachNewThreadSelector:@selector(NOME_FUNZIONE) toTarget:self withObject:nil];
Sostituiamo quindi il metodo refreshPhotoList con questi tre metodi:
- (IBAction)refreshPhotoList
{
[act startAnimating];
[NSThread detachNewThreadSelector:@selector(getXML) toTarget:self withObject:nil];
}
- (void)getXML
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
interestingness *inter = [[interestingness alloc] initWithApiKey:@"bd4890c606144764897cd145bd880d95"];
[inter downloadXML];
URLbuilder *urlBuilder = [[URLbuilder alloc] initWithXMLdata:[inter getinterestingnessXML]];
[urlBuilder startParsing];
urlArray = [[NSArray arrayWithArray:[urlBuilder getURLarray]] retain];
[inter release];
[urlBuilder release];
[self performSelectorOnMainThread:@selector(getXMLterminated) withObject:nil waitUntilDone:NO];
[pool drain];
}
- (void)getXMLterminated
{
[self viewPhotoAtIndex:0];
[act stopAnimating];
}
Analizziamoli insieme. Nel primo metodo, refreshPhotoList, adesso ci limitiamo a creare un thread separato in cui verrà eseguito il metodo getXML.
Il metodo getXML una volta invocato verrà eseguito su un thread separato quindi è necessario istanziare un nuovo oggetto di tipo NSAutoreleasePool che si occuperà della gestione della memoria.
Potete leggere questo articolo a tal proposito.
Però vogliamo essere avvisati quando il thread ha finito il suo compito, se non altro perchè dobbiamo far smettere di girare la rotellina!
utilizziamo quindi la funzione:
[self performSelectorOnMainThread:@selector(getXMLterminated) withObject:nil waitUntilDone:NO];
per eseguire il metodo getXMLterminated nel threadprincipale.
Quando questo metodo quando viene invocato è sintomo che il threadsecondario ha terminato il suo lavoro, quindi possiamo invocare i metodi necessari per visualizzare l’immagine.

Se avete problemi con il tutorial, questo è il nostro file di progetto.
NOTA: nel progetto di esempio allegato ho modificato le funzioni “prevPhoto” e “nextPhoto” per essere eseguite su thread separati.
Spero che questi tutorial siano stati utili, attendo i vostri commenti e le vostre critiche.











10 Responses to “T#051 – Accedere a Flickr dalle nostre applicazioni iPhone (parte 4)”
16 Luglio 2010
pippoIl link per il download non funziona…
1 Settembre 2010
MatteoIl link per scaricare il progetto non funziona 🙁
18 Ottobre 2010
stefanoIl link non funziona..
5 Novembre 2010
Staff devAPPAggiornato, scusate!! Ora dovrebbe andare ^^
9 Marzo 2011
LuigiCiao, complimenti per l’interessantissimo tutorial. Avrei qualche domanda da farti riguardo il progetto già pronto che ho scaricato:
– se lo mando in esecuzione ottengo 3 warning:
1) warning: class ‘URLbuilder’ does not implement the ‘NSXMLParserDelegate’ protocol
2) warning: control reaches end of non-void function
3) warning: ‘devFlickrViewController’ may not respond to ‘-viewPhotoAtIndex:’
– volevo provare anzichè la lista delle foto più interessanti di flickr a visualizzare le foto di un gruppo. Ho trovato l’api flickr.groups.pools.getPhotos e provandola ho visto che come struttura per i risultati è la stessa. Ho provato quindi a cambiare la costante FLICKR_INTERESTIGNESS_GETLIST_URL mettendo l’url (funzionante) che mi restituisce l’xml delle foto che mi interessano. Pero’ l’applicazione si chiude. Ho cambiato anche la key in initWithApiKey, e cmq è compresa anche nella richiesta. Cosa ho sbagliato? Grazie ancora.
10 Marzo 2011
ignaziocciao Luigi:
Per quel che riguarda i warning: io ne ottengo solo due:
‘devFlickrViewController’ may not respond to ‘viewPhotoAtIndex:’
indica che il metodo viewPhotoAtIndex non è stato trovato nell’header della classe, basta quindi aggiungere:
– (IBAction)viewPhotoAtIndex:(NSNumber *)position nel file devFlickrViewController.h
e ottengo anche “2) warning: control reaches end of non-void function”
in effetti deve essere qualche modifica al codice, quel metodo non restituisce nulla invece nel nome del metodo c’è scritto che restituisce un array..quindi può essere sostituito con un:
– (void) startParsing (sia nel file .m sia nel file .h)
per quanto riguarda l’ultimo warning (il tuo n.1) se non ricordo male c’era una differenza tra alcune versioni dell’sdk, ma cmq significa semplicemnte che la classe URLbuilser non esplicita la conformità a NSXMLParserDelegate, anche se poi lo utilizziamo proprio come delegate di un parser.
per risolver il warning aggiungere nell’intestazione della classe.
10 Marzo 2011
ignaziocPer quanto riguarda la modifica che hai fatto, mi risulta difficile capire il motivo, ma da quello che dici mi sembra che stai usando due volte l’apikey nella url REST. può essere?
14 Marzo 2011
LuigiGrazie Ignazio, è proprio come dici tu. Poi me ne sono accorto da solo rivedendo meglio come era costruita la stringa. Per i warning appena posso controllo quello che mi hai scritto.
Una richiesta allo staff: perchè sullo stesso tema non realizzate un tutorial su come visualizzare le foto di un album di facebook?
5 Novembre 2011
spagper il download del xml in un altro thread non si potrebbe usare pure GCD? tipo dispatch_queue_t per creare l’operazione e dispatch_async per effettuarla in multithreading?
Ovviamente entra in gioco l’uso dei blocks.
6 Novembre 2011
IgnaziocOvviamente si.
CI sono diversi modi per eseguire operazioni in un thread secondario, GDC è uno di questi (e non era ancora stato introdotto quando ho scritto questo articolo)