T#051 – Accedere a Flickr dalle nostre applicazioni iPhone (parte 4)
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:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | - (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:
1 2 3 4 5 6 7 8 9 10 | - (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
1 | [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:
1 2 3 4 5 6 7 8 9 10 11 12 13 | - (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:
1 2 3 4 | - (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.
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 | - (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]; [alert show]; [alert release]; } |
a questo punto la funzione per il salvataggio delle foto diventa:
1 2 3 4 | - (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:
1 | [NSThread detachNewThreadSelector:@selector(NOME_FUNZIONE) toTarget:self withObject:nil]; |
Sostituiamo quindi il metodo refreshPhotoList con questi tre metodi:
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 | - (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:
1 | [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.



















Il link per il download non funziona…