Il 0% ha trovato utile questo documento (0 voti)
341 visualizzazioni78 pagine

Iphone Programming

Caricato da

Elisabetta Sergi
Copyright
© Attribution Non-Commercial (BY-NC)
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Il 0% ha trovato utile questo documento (0 voti)
341 visualizzazioni78 pagine

Iphone Programming

Caricato da

Elisabetta Sergi
Copyright
© Attribution Non-Commercial (BY-NC)
Per noi i diritti sui contenuti sono una cosa seria. Se sospetti che questo contenuto sia tuo, rivendicalo qui.
Formati disponibili
Scarica in formato PDF, TXT o leggi online su Scribd
Sei sulla pagina 1/ 78

Approfondimenti tematici

iPhone

PROGRAMMING
Il corso completo per imparare facilmente a programmare lo smartphone Apple

iPhone

programming
Questo approfondimento tematico pensato per chi vuol imparare a programmare e creare software per lApple iPhone. La prima parte del testo guida il lettore alla conoscenza degli strumenti necessari per sviluppare sulla piattaforma mobile di Cupertino. Le sezioni successive sono pensate per un apprendimento pratico basato su esempi di progetto: la creazione di un browser su misura, la gestione dellinterfaccia, la programmazione di unagenda e di una to do list, la gestione corretta di celle e tabelle, lutilizzo dellaccelerometro, la progettazione di un RSS reader e via dicendo. Una serie di esempi pratici da seguire passo passo che creando applicazioni testabili e perfettamente funzionanti - spingono il lettore a sperimentare sul campo il proprio livello di apprendimento e lo invitano a imparare divertendosi.

CREARE SOFTWARE PER LAPPLE IPHONE . . . . . . . . . . . . . .4 Inizia da questo numero un corso dedicato allo sviluppo dellapple iphone, lo smartphone che ha rivoluzionato il mondo mobile. in questo primo appuntamento faremo la conoscenza di tutti gli strumenti necessari APPLE IPHONE PROGRAMMING . . . . . . . . . . . . . . . . .11 Continuiamo il corso inerente la programmazione del dispositivo smartphone apple iphone, illustrando gli strumenti necessari per completare lapplicazione mini browser avviata nel numero precedente della rivista LA NOSTRA APP SULLAPPLE STORE (2) . . . . . . . . . .11 pubblicare unapplicazione su apple store adoperando i portali iphone provisioning portal e itunes connect: scopriamo come farlo al meglio, seguendo tutto il percorso che arriva fino alla pubblicazione CREARE SOFTWARE PER LAPPLE IPHONE . . . . . . . . . . . . .17 In questa terza lezione modifichiamo il minibrowser introdotto nel precedente articolo della serie, introducendo altri concetti fondamentali come la gestione degli aspetti grafici e lutilizzo del componente uiwebview REALIZZIAMO UNA AGENDA PER IPHONE . . . . . . . . . . . .23 Da questo numero inizia una trattazione che un po il cuore di quasi ogni applicazione iphone. stiamo parlando delle tabelle. vedremo come si creano, quali tipologie utilizzare a seconda del contesto, e come gestirle al meglio IPHONE: GESTIONE DELLA MEMORIA . . . . . . . . . . . . . . . .29 Per chi si accinge a sviluppare applicazioni per lo smartphone di casa apple, uno degli argomenti sicuramente pi ostici, la gestione della memoria. In questo articolo affronteremo proprio questa importante tematica LEAK E ZOMBIE IN AGGUATO . . . . . . . . . . . . . . . . . . . . .35 In questo articolo approfondiremo ulteriormente i concetti legati alla gestione della memoria, in modo particolare faremo la conoscenza di quelli che in gergo vengono chiamati zombie e leaks

COME POPOLARE UNA UITABLEVIEW . . . . . . . . . . . . . .20 In questo articolo popoliamo la nostra tabella con un elenco di voci ottenute interrogando una serie di strutture dati. nella fattispecie vedremo come inserire delle semplici righe, creare delle sezioni e navigare tra le stesse GESTIONE DELLE CELLE NELLE TABELLE . . . . . . . . . . . .47 In questo articolo trattiamo delle varie funzionalit offerte dallsDk per inserire e cancellare una o pi righe nelle tabelle: elemento fondamentale nella costruzione delle interfacce, sia per linput che per la visualizzazione dei dati UNA SVEGLIA DIGITALE PER IPHONE . . . . . . . . . . . .53 In questo articolo mostriamo come realizzare una sveglia digitale che ci avviser di impegni e scadenze imminenti. sar loccasione di approfondire i concetti legati alla gestione dellinterfaccia e del timer UN ACCELEROMETRO PER AMICO . . . . . . . . . . . . . . . . . . . . .59 Laccelerometro presente nelliphone uno dei componenti pi efficaci nel consentire allutente uninterazione con giochi e applicativi maggiormente semplice e intuitiva. vediamo come integrarlo nelle nostre applicazioni IPHONE: GESTIRE IL MULTI-TOUCH . . . . . . . . . . . . . . . .63 Lo schermo multitouch consente di interagire con un dispositivo senza la necessit di fornire ingombranti tastiere. impariamo a intercettare le azioni degli utenti e a gestirle in modo da non far rimpiangere tastiera e mouse UN RSS READER SU IPHONE . . . . . . .68 Distribuire informazioni attraverso le nostre applicazioni pu essere molto pi semplice adoperando la classe nsxmlparser. saremo cos in grado di raccogliere e mostrare i contenuti degli rss consumando pochissima banda UN RSS READER PER IPHONE . . . . . .73 Adoperando la classe nsxmlparser, possiamo interagire con i contenuti di un feed rss. in questo articolo mostreremo come fare e come implementare una struttura dati per la visualizzazione delle info ricevute

Imparare a programmare lApple iPhone iPhone programming Imparare a programmare lApple iPhone MOBILE

CREARE SOFTWARE PER LAPPLE IPHONE


causa della NDA (Non Disclosure Agreement) che nel mese di luglio dello scrso anno ha accompagnato la distribuzione dell'SDK, parallelamente all'uscita in Italia dell'iPhone, non ci stato possibile pubblicare i successivi articoli pianificati per il seguente corso di programmazione di questo interessante dispositivo. Negli ultimi mesi sono state apportate numerose migliorie al firmware (terminate con il rilascio della versione stabile 2.2.1), e recentemente l'OS 3.0 con il relativo SDK 3.0, (in fase di beta 3 al momento della scrittura di questo articolo), fornir ancora pi strumenti per i programmatori che desiderano utilizzare al meglio le funzionalit di questa piattaforma di sviluppo. Molti concetti fondamentali, tra cui architettura dell'iPhone, su come strutturato il framework, la disposizione delle cartelle dei progetti creati con Xcode, inclusi numerosi consigli di ottimizzazione, sono stati gi affrontati nel numero 130 di questa rivista. Consigliamo a questo proposito di ridare una lettura a tali pagine, perch non verranno trattati, se non quando sar strettamente necessario. Ricordiamo, inoltre, che possibile realizzare un software completamente funzionante senza avere acquistato alcun iPhone o iPod Touch di seconda generazione, e senza acquistare la licenza. Di contro, si devono accettare le seguenti limitazioni: in alcune situazioni tale software, eseguito all'interno dell'emulatore, non si comporter in maniera fedele al vero dispositivo, quello fisico, rendendo addirittura inutilizzabile il vostro prodotto. Questo dovuto sia al fatto che l'emulatore suddetto viene eseguito utilizzando le risorse hardware (nettamente pi prestanti sotto ogni aspetto) del vostro computer, sia perch manca di alcune funzionalit, come il GPS e l'accelerometro ad esempio, sia per alcune discrepanze che molti programmatori hanno riscontrato nei vari test; se non acquisterete una licenza non potrete testarlo su alcun iPhone/iPod e non potrete venderlo sull'Apple Store. La scelta se utilizzare un

INIZIA DA QUESTO NUMERO UN CORSO DEDICATO ALLO SVILUPPO DELLAPPLE IPHONE, LO SMARTPHONE CHE HA RIVOLUZIONATO IL MONDO MOBILE. IN QUESTO PRIMO APPUNTAMENTO FAREMO LA CONOSCENZA DI TUTTI GLI STRUMENTI NECESSARI

iPhone o un iPod Touch spetta a voi, un iPod Touch di seconda generazione comunque valido, poich ha un hardware leggermente pi prestante rispetto al fratello, manca per delle funzionalit GPS, UMTS e fotocamera; come detto precedentemente lo sblocco del Bluetooth con il firmware 3.0 nell'iPod Touch ha eliminato questa ulteriore differenza tra i due dispositivi.

REGISTRAZIONE E SDK
Per iniziare a programmare necessario effettuare due operazioni: la prima consiste nel registrarsi come sviluppatore, divenire quindi Registered iPhone Developer, operazione completamente gratuita. Baster accedere al seguente indirizzo
http://developer.apple.com/iphone/

cliccare poi sulla voce in alto, register, e decidere se creare un nuovo account, oppure, nel caso vi siate gi registrati utilizzando mobileMe, l'itunes Store, l'ADC (Apple Developer Connection) o Apple Online Store, utilizzare il vostro identificativo corrente. A questo punto necessario inserire tutte le informazioni anagrafiche, accettare

REQUISITI
Conoscenze richieste OOP Software Mac OS X 10.5 o superiore Impegno

Tempo di realizzazione

Fig. 1: La home page del sottosito dedicato allo sviluppo di applicazioni per iPhone

4 G 30 /Giugno 2009

h t t p : / / w w w. i o p r o g r a m m o . i t i Phone pr o g r am m in g

Imparare a programmare lApple lApple iPhone Imparare a programmare iPhone

MOBILE iPhone programming

Fig. 3: La struttura della cartella di installazione dell'SDK

Fig. 2: La fase di registrazione, in cui sono richieste tutte le vostre informazioni anagrafiche

l'informativa e, alla ricezione dell'email di conferma, seguire il link che vi verr inviato: siete cos divenuti Registered iPhone Developer. La seconda, ed ultima, operazione da effettuare, consiste nello scaricare e installare sul vostro Mac l'SDK; la versione stabile, 2.2.1, consiste in un file di circa 1.7GB, consigliamo quindi di utilizzare una connessione relativamente veloce; inoltre necessario avere almeno Leopard aggiornato alla versione 10.5.4 e disporre di sufficiente spazio sul proprio disco, poich, inclusa la documentazione, verranno occupati oltre 5GB. All'avvio dell'installer sar richiesto quali componenti installare: quelli di default sono sufficienti. Al termine di questo non breve processo le librerie, tutto il software necessario, tra cui il simulatore, Xcode, l'editor per i progetti, Interface Builder, l'editor WYSIWYG, Shark e Instruments, necessari per il tuning delle vostre applicazioni, compresa una versione parziale della documentazione saranno contenuti all'interno della cartella Developer. All'interno della cartella Applications trovano posto quegli strumenti che adopererete in ogni progetto: XCode, Interface Builder, Shark (sottocartella Perfor mance Tools) e Instruments; poich da Xcode possibile richiamare tutti gli altri applicativi, baster trascinare solo tale software nel Dock per avere disponibile tutti questi programmi con un colpo di clic. La documentazione non viene
h t t p : / / w w w. i o p r o g r a m m o . i t i Ph on e p r o g r a m m ing

installata completamente, e si ha libera scelta se consultarla richiamando le pagine disponibili online, prelevate automaticamente quando necessario, risparmiando spazio sul proprio HD, o scaricarla localmente; in questo ultimo caso, sar necessario avviare XCode, selezionare il menu Help->Documentation e cliccare sui singoli tasti get che si trovano vicino alle singole categorie; sono inoltre disponibili documenti riguardanti la libreria WebObjects (bisogna selezionarla tra i componenti aggiuntivi durante la fase di installazione) e le API Java 1.4 e 1.5, questo perch la versione di Xcode (e degli altri software installati) consente anche di realizzare applicativi, plugin, moduli aggiuntivi per Mac OS in Java, C++, Ruby e altri linguaggi. La versione 2.2.1 non dotata di documentazione, poich stata rilasciata per correggere alcuni bug, non quindi avvenuta alcuna modifica all'API, per tale motivo la documentazione pi aggiornata corrisponde a quella fornita con la 2.2. Utilizzando il campo di testo presente in alto a destra, si ricercheranno quelle funzioni e/o metodi disponibili per una consultazione; possibile inoltre filtrare su quale API effettuare la ricerca.

NOTA

RIFERIMENTI WEB

Per la creazione dell'account e per scaricare l'SDK, visitate il seguente percorso web:
http://developer.apple.co m/iphone/

Allindirizzo che segue trovate una descrizione dettagliata delle novit fornite con il nuovo SDK 3.0:
http://developer.apple.co m/iphone/prerelease/libra ry/releasenotes/General/W hatsNewIniPhoneOS/Articl es/iPhoneOSv3.html

INIZIAMO LAVVENTURA
In questa prima puntata creeremo un browser web minimale che ci permetter di conoscere i concetti fondamentali di questa tecnologia, progressivamente presenteremo le nozioni necessarie per completare il progetto e renderlo pienamente operativo; non creeremo nuove classi per evitare confusione, ma utilizzeremo quelle fornite quando si crea un nuovo software usando il wizard; ricordiamo che la struttura delle classi con cui lavoreremo organizzata con una struttura ad albero (Fig.2), avente una radice (NSObject al pi alto livello, UIWindow scendendo di qualche gradino) a cui si associano i vari

Giugno 2009/ 31 5G

Imparare a programmare lApple iPhone MOBILE iPhone programming Imparare a programmare lApple iPhone
interazione con tali dati; lo strumento per realizzarle Interface Builder (utilizzando i file xib) oppure puro codice Objectice-C (creando tutte le strutture grafiche all'interno di file .m/.h); con Controller intendiamo quella parte di codice necessaria per una corretta comunicazione tra le due strutture precedenti: , di fatto, la componente in cui risiede l'intelligenza del vostro software (e dove ovviamente riscontrerete la quasi totalit dei problemi e degli errori), provvedendo a creare un mapping tra cosa devono mostrate i vari componenti grafici e in che modo devono essere modificati conseguentemente a un'interazione da parte dell'utilizzatore del vostro software; in questo contesto il Controller (anche qui non a caso identificato con la classe UIViewController) corrisponde al codice presente nei file .m/.h, realizzati con il linguaggio Objective-C ( possibile comunque inserire codice C e C++ nel caso fosse necessario velocizzare alcune operazioni o per utilizzare le API a livello pi basso).

NOTA

Model-View-Controller (MVC, talvolta tradotto in italiano Modello-VistaControllore) un pattern architetturale molto diffuso nello sviluppo di interfacce grafiche di sistemi software object-oriented. Viene sovente utilizzato da framework basati su Ruby, Java (Swing, JSF e Struts), su Objective C o su .NET. Il pattern basato sulla separazione dei compiti fra i componenti software che interpretano tre ruoli principali: - il model fornisce i metodi per accedere ai dati utili all'applicazione; - il view visualizza i dati contenuti nel model e si occupa dell'interazione con utenti e agenti; - il controller riceve i comandi dell'utente (in genere attraverso il view) e li attua modificando lo stato degli altri due componenti

DESIGN PATTERN MVC

Fig. 4: La struttura ad albero dell'API

UIView Controller (contenitori invisibili UIVIEW) e i cui figli sono discendenti della classe UIView (bottoni, testi e altri componenti). L'approccio migliore per iniziare a programmare l'iPhone/iPod Touch quello top-down, adoperando quei componenti dell'interfaccia grafica predefiniti, disponibili attraverso l'Interface Builder (IB), aggiungendo, quando necessario, il codice minimale richiesto per ottenere il risultato desiderato. Successivamente si potr, nel caso lo si trovasse limitante oppure per semplici gusti personali, abbandonare completamente tale strumento visuale, e realizzare tutto in puro Objective-C. Riprendiamo brevemente il discorso, iniziato nell'articolo di presentazione sull'iPhone, pubblicato a settembre e riguardante il design pattern MVC; dovrete metabolizzare tale concetto per non riscontrare problemi nella fase di sviluppo del vostro primo progetto. MVC acronimo di Model View Controller, indica i tre componenti utilizzati per realizzare applicazioni in grado di adattarsi meglio a variazioni della propria struttura interna; con Model si indica la/le struttura/e dati utilizzata/e per gestire le informazioni necessarie per un corretto funzionamento del vostro software, possono essere variabili, array, strutture pi complesse, file locali o remoti e anche database; con View (che non a caso viene identificata con la classe UIView) si indica l'interfaccia grafica necessaria per consentire all'utente finale una consultazione e/o

Fig. 5: Una generalizzazione del design pattern MVC utilizzato

CREAZIONE DEL PROGETTO E XCODE


Il progetto Xcode fornito dal Wizard, che ci consente di realizzare il nostro browser nella maniera pi veloce, utilizzando un'istanza di un UIViewController contenente una della classe UIView si chiama View-Based Application. Decidiamo il nome dell'applicativo, e posizioniamolo in una cartella a nostra scelta. Al termine della creazione ci verr presentata la schermata di Fig.7. Analizziamola in breve: suddivisa principalmente in tre aree: nella parte alta troviamo i comandi necessari compilare il nostro software, selezionare la piattaforma di testing, simulatore o dispositivo fisico (nel caso abbiate gi acquistato la licenza), nella colonna di sinistra troviah t t p : / / w w w. i o p r o g r a m m o . i t i Phone pr o g r am m in g

6 G 32 /Giugno 2009

Imparare a programmare lApple lApple iPhone Imparare a programmare iPhone

MOBILE iPhone programming

Fig. 6: La finestra di scelta del progetto

La cartella frameworks contiene le librerie che si desidera inserire nel progetto, le tre fondamentali, inserite automaticamente, sono UIKit.framework, Fondation.framework e Core Graphics.framework; per aggiungerne altre baster trascinarle all'interno del progetto, oppure utilizzare il destro del mouse e selezionare Add Existing Frameworks. Le altre cartelle virtuali, rappresentate con differenti icone, svolgono principalmente funzione di raggruppamento automatico di diversi tipi di informazioni, errori di programmazione, file grafici xib, bookmark, ad esempio. Premendo CTRL + INVIO, oppure selezionando l'icona Build & Go, o dal menu Build la voce Build & Run avvieremo il simulatore.

mo tutti i file del progetto, comprese le librerie utilizzate, in quella destra viene visualizzato il contenuto del file selezionato a sinistra. Per aggiungere un file al nostro progetto sufficiente crearlo utilizzando il menu File->Nuovo File, oppure ne trasciniamo uno preesistente all'interno della colonna di sinistra, prelevandolo da una finestra di Finder; possibile utilizzare anche il comando Add to Project presente all'interno del menu Project, presenter una finestra da cui selezionare uno o pi file presenti nel proprio computer: questa operazione necessaria quando si creano risorse con software diversi come audio, video o immagini. Copiare tramite Finder un file all'interno della cartella del nostro progetto non sufficiente, poich XCode richiede che i file che dovr compilare vengano aggiunti utilizzando una delle modalit suddette: quindi possibile inserire qualunque tipo di file nella cartella del progetto e aggiungere solo un sottoinsieme di tali elementi in XCode. Dopo aver selezionato il file ci verr presentata una finestra in cui potremo selezionare il checkbox corrispondente alla voce Copy items into destination group's folder (if needed), tale operazione non obbligatoria, ma in caso non la si attivasse non si copierebbe localmente, all'interno della cartella del progetto e si lavorerebbe con un link simbolico; una cancellazione o uno spostamento del file originale (quello fisico) dalla sua posizione ( situato in una diversa cartella del proprio computer) non permetterebbe di compilare il progetto; selezionando quindi tale opzione, abbiamo la garanzia di avere all'interno della cartella del progetto tutti i file necessari, rendendo estremamente veloce qualunque operazione di backup o invio ad altri sviluppatori. Analizziamo ora la finestra di sinistra. Noterete alcune cartelle, di colore giallo, queste non esistono fisicamente nel vostro computer, si chiamano Group(s) e hanno lo scopo di consentire un raggruppamento visivo delle proprie risorse, sono delle cartelle virtuali, possibile quindi crearle liberamente utilizzando il tasto destro del mouse (Add->New Group) o dal menu Project.
h t t p : / / w w w. i o p r o g r a m m o . i t i Ph on e p r o g r a m m ing

I FILE XIB E LINTERFACE BUILDER


Ora che abbiamo creato un progetto, iniziamo a descrivere i due componenti fondamentali che sono onniprensenti nei progetti per iPhone, in maniera diretta e/o indiretta (utilizzando componenti che da questi derivano): UIViewController e UIView. Ogni istanza di UIViewController invisibile, e il suo scopo quello di contenitore delle UIView (a cui appartengono tutti i componenti visuali, bottoni, testi inclusi) che in esso andremo a inserire, pu svolgere anche ruolo di gestore degli eventi, rafforzando ulteriormente il concetto di come nell'MVC il Controller sia trasparente, non abbia quindi un aspetto grafico, ma sia puramente codice. Dopo avere creato il progetto troveremo due file xib all'interno della colonna di sinistra di XCode, un primo chiamato MainWindow.xib, il quale contiene la UIWindow, la root del nostro software, che provvede automaticamente a contenere e gestire tutte le sottofinestre (e la catena di eventi), nel nostro caso sono un viewcontroller contenente una view, e un secondo file il cui nome, terminante per ViewController, dipender da quel-

NOTA

INTERFACE BUILDER

Interface Builder un'applicazione facente parte di Xcode. Consente agli sviluppatori che usano Carbon e Cocoa di disegnare interfacce grafiche per le applicazioni usando uno strumento grafico, senza la neccessit scrivere nessuna riga di codice. L'interfaccia risultante salvata in un file .nib (abbreviazione di NeXT Interface Builder).

Fig. 7: L'interfaccia di sviluppo proposta dal tool XCode

Giugno 2009/ 33 7G

Imparare a programmare lApple iPhone MOBILE iPhone programming Imparare a programmare lApple iPhone

NOTA

iPhone OS 3.0, la cui release ufficiale dovrebbe essere rilasciata in questi giorni, include un Software Developer Kit (SDK) aggiornato con oltre 1000 nuove Application Programming Interface (API) tra cui In-App Purchases, connessioni Peer-to-Peer, una interfaccia applicativa per gli accessori, accesso alla libreria musicale delliPod, un nuovo Map API e le notifiche Push. Saranno disponibili oltre 100 nuove funzioni per gli utilizzatori di iPhone e iPod touch, tra cui il Taglia, Copia e Incolla (che potranno essere utilizzate allinterno e tra le applicazioni), gli MMS (disponibili solo su iPhone 3G) per spedire e ricevere immagini, contatti, file audio e posizioni geografiche con la funzione Messaggi, il Bluetooth stereo, la sincronizzazione delle note con Mac e PC; shake per attivare la funzione Shuffle, il controllo parentale per i programmi televisivi, i film e le applicazioni dellApp Store e il login automatico agli hot spot Wi-Fi.

APPLE SDK IPHONE 3.0

Fig. 8: L'interfaccia di Interface Builder, il wizard che consente di disegnare la nostra applicazione

lo del nostro progetto, nel nostro caso ioPro grammoArt2Viewcontroller.xib; quest'ultimo file quello che ci interessa e che andremo a modificare visivamente con Interface Builder. La nostra UIView, al cui interno andremo a posizionare alcuni componenti visuali (pulsanti, testi, immagini), ha necessit di essere ospitata in un UIViewController (o classe derivata) per essere mostrata su schermo, in questo caso tale operazione stata gestita automaticamente dal wizard, ma in altri casi necessario creare tramite IB tale relazione, oppure adoperando Objective-C. In un progetto sviluppato con XCode possibile creare un numero indeterminato di file xib, realizzati adoperando Interface Builder, l'editor WYSIWYG disponibile tra i software forniti nell'SDK; ogni file con estensione xib in realt un semplice XML (per chi avesse utilizzato Adobe Flex un approccio simile a quello dei file MXML, mentre per chi conosce Windows Presentation Foundation simile, sempre concettualmente parlando, a quello ideato per i file XAML) che viene letto, interpretato e mostrato in maniera visuale da IB. Diamo uno sguardo veloce alla struttura di uno di questi file, premendo il destro del mouse selezioniamo la voce Open as Souce Code File, otterremo il seguente codice (qui ridotto per la sua estrema lunghezza):
<?xml version="1.0" encoding="UTF-8"?> <archive type="com.apple.InterfaceBuilder3. CocoaTouch.XIB" version="7.02"> <data> <int key="IBDocument.SystemTarget">528</int> <string key="IBDocument.SystemVersion">9E17</string> <string key="IBDocument.InterfaceBuilder Version">672</string>

<string key="IBDocument.AppKitVersion">949.33</string> <object class="IBUIView" id="774585933"> ... <string key="NSFrameSize">{320, 460}</string> <object class="NSColor" key="IBUIBackgroundColor"> .. <int key="NSColorSpace">1</int> <bytes key="NSRGB">MC44MDQzNDc4 MSAwLjM5MzQ0OTI4IDAuMjk4MDAzOTcAA</bytes> </object> ... <reference key="object" ref="774585933"/> <reference key="parent" ref="360949347"/> <string key="objectName">ViewPrincipale</string> . </data> </archive>

Un file XML relativamente complesso, ma basta fare qualche test all'interno di IB per verificare in che modo questa struttura viene modificata; se notate, la riga <object class="IBUIView" id=" 774585933"> contiene al suo interno molte delle informazioni riguardanti la view che stata creata automaticamente, ad esempio le dimensioni, il colore di sfondo e quali componenti vengono simulati visivamente (spiegheremo pi in avanti tale funzionalit); il numero ID viene usato in altre locazioni di tale file per riferimenti a questo stesso oggetto (come avviene in una delle ultime righe <reference key="object" ref="774585933"/. Modificare direttamente tale file inutile, poich IB serve proprio a questo, inoltre si rischia di renderlo inutilizzabile, ma sapere almeno che un formato leggibile da qualunque utente e non codificato/compilato, pu risultare utile in alcuni casi,
h t t p : / / w w w. i o p r o g r a m m o . i t i Phone pr o g r am m in g

G 34 /Giugno 2009 8

Imparare a programmare lApple lApple iPhone Imparare a programmare iPhone

MOBILE iPhone programming

come quando si corrompe per qualche motivo (crash di IB, del sistema operativo, o della locazione fisica in cui viene memorizzato) e non si riesce pi a modificare una (o pi) propriet di un (o pi) componente da IB. Questo formato viene automaticamente convertito in fase di compilazione in maniera trasparente. Cliccando invece due volte sul tale file avvieremo automaticamente IB. Partendo da sinistra e andando verso destra abbiamo: la finestra di anteprima, la Document Window e l'Inspector; la prima responsabile della visualizzazione dell'anteprima dell'interfaccia grafica, mostrer quindi tutti i componenti grafici che inseriremo con una semplice operazione di trascinamento dalla finestra chiamata Library. La seconda finestra che incontriamo, la Document Window, mostra un elenco di tutti gli oggetti presenti nel file .xib, sia grafici che non, ci permette, inoltre, di selezionarli e poter cos visualizzare i dettagli all'interno della finestra alla sua destra, l'Inspector; la chiusura della Document Window comporta la chiusura di tutto il file xib in IB, rendendo quindi necessario riaprire tale file dal menu File->Open o facendo nuovamente doppio clic in XCode. I tre componenti visibili nel Document Window inizialmente sono il file owner, il cui scopo quello di identificare quale classe (la coppia .m/.h) collegata al file .xib che stiamo usando (sotto la colonna type risulta ioProgrammoArt2ViewController, creata automaticamente dal wizard), first responderer utilizzato per gestire la catena di eventi, e la nostra View, che in questo caso non ha alcuna coppia di file .m/.h associata (basta notare che sotto la colonna Type risulta UIView invece di un nome di classe creato manualmente dal sottoscritto). Le quattro sottofinestre dell'Inspector forniscono tutte le informazioni riguardanti un determinato componente selezionato. Cliccando sulla view, utilizzando il Document Window abbiamo ottenuto i seguenti risultati. Attributes Inspector: nella parte alta troviamo la sezione chiamata view simulated bar metrics, che permette di aggiungere solo visivamente alcuni componenti grafici, tipici di classi derivate da UIViewController pi complesse, come il Tab Bar Controller e il Navigation Controller, in modo da fornire un feeback visivo di come tale view cambier le proprie proporzioni all'interno dei contenitori; lanteprima non modifica in alcun modo la struttura effettiva della view, un semplice strumento di valutazione. Nella parte bassa sono presenti opzioni come la trasparenza, il colore della view, come questa si adatta in funzione delle dimensioni settate, se consente l'interazione con l'utente, e altre funzioni specifiche a seconda della sottoclasse di UIView utilizzata (ad esempio, un UIButton, un semplice bottone, ha propriet diverse di un UILabel, il cui scopo solo di visualizzare
h t t p : / / w w w. i o p r o g r a m m o . i t i Ph on e p r o g r a m m ing

NOTA

ALTERNATIVE A MAC OSX E XCODE

Fig. 9: Come si identificano i componenti dell'MVC utilizzando un file XIB

del testo); spiegheremo in dettaglio tutti questi campi nei prossimi articoli a seconda delle esigenze. Nel Connection Inspector avremo modo di visualizzare i collegamenti tra view e controller (ma non solo). Nella Size Inspector troviamo i tipici controlli di dimensionamento e allineamento. Nell'Identity Inspector trovano posto tutte le informazioni riguardanti relazioni ed eventi gestiti dalla view, compresa la classe che la gestisce (si parler di IBOutlet e IBAction). Come spiegato precedentemente, IB genera file che appartengono al componente del pattern MVC chiamato View, non c' modo diretto di scrivere all'interno di questo editor codice Objective-C, per tale motivo dobbiamo creare una coppia di file per ogni componente grafico con il quale vogliamo che l'utente interagisca (sia derivato da UIViewController che da UiView), o che desideriamo animare o trasformare in qualche modo, e associare in Interface Builder questa coppia di file

IBM ha rilasciato una propria guida allo sviluppo di applicazioni per iPhone, utilizzando come sistema operativo Windows o Linux. La guida si intitola Write native iPhone applications using Eclipse CDT. Si tratta di un PDF in lingua inglese che possibile scaricare gratuitamente da questo indirizzo web:
http://www.ibm.com/deve loperworks/edu/os-dw-oseclipse-iphone-cdt.html.

Per lo sviluppo vengono utilizzati Eclipse e Cygwin, tuttavia, le applicazioni sviluppate secondo questo schema non possono essere pubblicate sull'AppStore, ma soltanto su sistemi come Cydia e Installer.

Fig. 10: LAttributes Inspector dellInterface Builder

Giugno 2009/ 35 9G

Imparare a programmare lApple iPhone MOBILE iPhone programming Imparare a programmare lApple iPhone
al componente visivo: i file in questione avranno estensione .h e .m, e rappresenteranno quindi il Controller del nostro componente (in questo progetto non diamo attenzione al Model). Questi due file sono complementari, e rappresentano un'unica classe realizzata con un linguaggio object oriented (categoria alla quale ovviamente l'Objective-C appartiene); con l'estensione .h si indica l'interfaccia della classe, rappresenta quindi quell'insieme di metodi e variabili che si desidera rendere disponibili esternamente, alle altre classi e a Interface Builder. Ecco come si presenta l'interfaccia del viewcontroller, il file .h, realizzato automaticamente quando si crea un nuovo progetto View-Based Application:
#import <UIKit/UIKit.h> @interface ioProgrammoArt2ViewController : UIViewController {} @end

Mentre quello che segue il file .m, sempre creato automaticamente quando si crea il nuovo progetto, sar:
#import "ioProgrammoArt2ViewController.h" @implementation ioProgrammoArt2ViewController /* - (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil {...} - (void)loadView {} - (void)viewDidLoad {... } */ /* (UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationPortrait); } */ - (void)didReceiveMemoryWarning { [super didReceiveMemoryWarning]; } - (void)dealloc { [super dealloc]; } @end -(BOOL)shouldAutorotateToInterfaceOrientation:

LAUTORE
Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali e non su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certificatd Expert, EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multidediali con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito www.leganza.it.

Quasi tutti i metodi sono commentati e, a seconda delle necessit, potrete decommentarli, o anche rimuoverli (non rimuovete gli ultimi due) vedremo nel prossimo articolo i loro possibili utilizzi. all'interno di quest'ultimo file che scriveremo il codice che gestir qualunque tipo di operazione; ogni volta che inseriremo la signature di un determinato metodo all'interno del file .h logicamente necessario creare la sua implementazione, quindi il suo codice effettivamente funzionante, all'interno del file .m. In questo caso stata creata (automaticamente) una classe associata ad un viewcontroller, che utilizzeremo per gestire l'interazione con

l'utente, ma possibile anche crearne un'altra derivante da UIView e associarla alla view contenuta nel viewcontroller, e demandare a questa la gestione dell'interazione con l'utente (ricordiamo che qualunque componente visuale pu avere associata una classe identificata da un file .h e .m); l'affermazione precedente valida poich entrambe le classi derivano da una comune, chiamata UI Responder, che consente di monitorare la pressione da parte dell'utente fornendo ad entrambe alcuni metodi: touchesBegan, touchesCancelled, touches Ended, touchesMoved. Il viewcontroller intercetta ogni input (il tocco con una o pi dita), poich in questo progetto la nostra UIView utilizzata semplicemente come contenitore di altri componenti visuali (infatti non stata creata alcuna classe che ne rappresenti il suo Controller del pattern MVC). Se vogliamo quindi consentire l'interazione umana con il ViewController, che contiene la nostra View, dovremo effettuare alcuni passi utilizzando IB. In primis creeremo un file .xib (in questo caso stato creato automaticamente), poi due file .h e .m, che estendono la classe che deve essere associata al componente grafico (anche in questo caso stato creato automaticamente), nel nostro caso un UIViewController. Sar quindi necessario intercettare la pressione dell'utente, oppure richiamare uno o pi metodi se stato premuto un pulsante o altro componente interattivo (keyword IBAction oppure adoperando delegate). Se desideriamo accedere facilmente nei nostri metodi a uno, o pi elementi (pulsanti, label, webview etc) presenti nel ViewController dovremo, utilizzando la keyword IBOutlet, associare una variabile nel file .h a tale elemento grafico. Il passo successivo consister nel collegare all'interno di Interface Builder questa nuova classe con il componente visuale (facendo uso dell'Identity Inspector), quindi collegare (opzionalmente) quali metodi vengono richiamati quando l'utente interagisce con un determinato pulsante/componente interattivo. Sembra complicato ma dopo qualche incertezza diventer un automatismo che eseguirete con estrema velocit. L'operazione di associazione di un compoenente visuale a una classe molto semplice: basta selezionare il componete grafico prescelto nel Document Window e successivamente selezionare la classe, precedentemente creata, all'interno del campo Class, contenuto nell'Identity Inspector. Nel prossimo articolo inseriremo un pulsante, una label e una webview, collegandoli tra loro utilizzando IBAction e IBOutlet, mostreremo l'alternativa fornita dall'utilizzo di delegate, gestendo tutto all'interno del file .m del ViewController. Forniremo inoltre una descrizione delle diverse tipologie di componenti grafici presenti. Andrea Leganza
h t t p : / / w w w. i o p r o g r a m m o . i t i Phone pr o g r am m in g

10 G 36 /Giugno 2009

Componenti Componenti grafici e iPhone programming CORSO MOBILE grafici e controllo UIWebView controllo UIWebView

APPLE IPHONE PROGRAMMING


N
CD WEB
corsoiphone2.zip
cdrom.ioprogrammo.it

CONTINUIAMO IL CORSO INERENTE LA PROGRAMMAZIONE DEL DISPOSITIVO SMARTPHONE APPLE IPHONE, ILLUSTRANDO GLI STRUMENTI NECESSARI PER COMPLETARE LAPPLICAZIONE MINI BROWSER AVVIATA NEL NUMERO PRECEDENTE DELLA RIVISTA

ell'articolo del numero precedente abbiamo creato un progetto View-Based Application utilizzando XCode, fornendo, inoltre, una breve descrizione di Interface Builder; in questo nuovo appuntamento, posizioneremo i componenti grafici, un bottone, un campo di testo e una UIWebView, ovvero un componente visuale che si comporta come un browser web, al pari del software Safari installato nell'iPhone. Seguiremo le procedure necessarie per rendere accessibili tali elementi all'interno della classe (file .h e .m) del ViewController, scrivendo infine il codice utile per farli interagire e rispondere agli eventi generati dal tocco dell'utente. L'applicativo consentir, al termine di

questo articolo, di digitare un qualunque indirizzo web nel campo di testo, rendendolo cos visualizzabile nella UIWebView.

DISPONIAMO GLI ELEMENTI GRAFICI


Avviamo Interface Builder facendo doppio clic sul file ioProgrammoArt2ViewController.xib, ci si presenter a video linterfaccia di Fig.2 Trasciniamo i tre componenti all'interno della UIView e ridimensioniamoli utilizzando i punti

Fig. 2: Come si presente il file xib senza componenti aggiuntivi

REQUISITI
Conoscenze richieste OOP Software Mac OS X 10.5 o superiore Impegno

presenti ai bordi: alcuni componenti non possono subire variazioni in alcune direzioni, come avviene per il campo di testo che pu variare di lunghezza e non di altezza. Per il bottone baster fare doppio clic sull'area che gli compete per impostare la stringa che

Tempo di realizzazione

Fig. 1: L'interfaccia che realizzeremo in questo articolo

Fig. 3: I punti utilizzabili per il ridimensionamento di un campo di testo


h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 30 /Luglio 2009

11

MOBILE Componenti controllo UIWebView iPhone programming grafici e Componenti grafici e controllo UIWebView

verr visualizzata come label, oppure aprire l'Attribute Inspector (CTRL +1) e impostare il campo Title; per impostare il colore verde della view principale basta selezionarla e cliccare sul tasto Background, che in questo caso ha assunto valori RGB 201,255,156. La classe di appartenenza di componenti visuali appena inseriti, la potrete identificare analizzando la colonna Type del Document Inspector di IB: UITextField, UIWebView e UIButton; queste tre informazioni ci permetteranno di creare delle variabili ad-hoc

con estensione .h, anteponendo alla classe di appartenenza usata nella dichiarazione della variabile, il termine IBOutlet, acronimo di Interface Builder Outlet; IBOutlet un qualificatore di tipo utilizzato solo per questo scopo, quindi una semplice macro vuota corrispondente alla riga in linguaggio C #define IBOutlet; tale istruzione, quando letta da Interface Builder, lo informa che vogliamo realizzare una connessione tra variabile Objective-C e oggetto visuale:
//file ioProgrammoArt2ViewController.h #import <UIKit/UIKit.h> @interface ioProgrammoArt2ViewController : UIViewController { IBOutlet UIWebView *webView; IBOutlet UITextField *addressField; IBOutlet UIButton *goButton;

NOTA

IL COMPONENTE UIWEBVIEW

Fig. 4: La classe dei singoli componenti visibile in corrispondenza della colonna type

} @end

nel prossimo paragrafo per utilizzarle nel codice Objective-C. Da questa immagine si ponga anche attenzione al tipo di struttura che abbiamo creato. La UIView principale, quella associata al nostro UIViewController, risulta la radice di un albero i cui tre figli sono proprio i tre componenti che abbiamo trascinato al suo interno. Parliamo ora di IBOutlet e IBAction, keyword che ci agiscono come bridge tra Interface Builder e XCode.

COS IBOUTLET
Ora che abbiamo disegnato quella che sar la nostra interfaccia grafica, dobbiamo realizzare il codice necessario per il funzionamento del nostro applicativo, quella responsabile del Controller, scrivendo istruzioni Objective-C all'interno della classe chiamata ioProgrammo Art2ViewController.m; prima di fare ci necessario spiegare come creare un collegamento tra i componenti visuali e oggetti in linguaggio Objectice-C, oggetti con i quali potremo interagire effettuando chiamate via codice: in questa fase che viene creato un collegamento esplicito tra View e Controller. Il modo pi semplice consiste nell'effettuare una procedura costituita da due semplici passi: creare tante variabili quanti sono i componenti grafici con i quali vogliamo comunicare, tre in questo caso, all'interno del file responsabile dell'interfaccia della classe, quello
h t t p : / / w w w. i o p r o g r a m m o . i t

Le tre variabili hanno come classe di appartenenza la stessa dei componenti visuali, che potrete identificare sotto la colonna Type del Document Inspector di IB, come stato mostrato nel paragrafo precedente. I tre IBOutlet sono caratterizzati da una tipizzazione forte (strongly typed) proprio a indicare che a queste variabili possono essere associati solo elementi grafici delle classi da noi indicate; una versione meno restrittiva (weakly typed) verr mostrata successivamente, quando forniremo una versione pi generica di IBAction (che nel prossimo articolo verr spiegato approfonditamente). Le variabili create hanno come modificatore di accesso di default @protected, inaccessibili all'esterno, ma utilizzabile dalle classi che derivano dalla nostra (che in questo contesto ininfluente, in un articolo successivo, descriveremo, tutti i modificatori disponibili). Questa prima parte della procedura ha un effetto immediato nella finestra delle propriet del componente visuale chiamato File Owner, al quale associata la classe ioProgra mmoArt2ViewController; accedendo quindi all'Ide ntity Inspector (CTRL+4) ritroveremo le tre variabili, all'interno della sezione chiamata Class Outlets, esposte dalla nostra classe. La seconda, e ultima parte della procedura, consiste nel premere il tasto destro del mouse dopo aver selezionato la riga corrispondente al File Owner, a cui associata la classe ioProgrammo Art2ViewController: tale operazione presenter una finestra semitrasparente nera nella quale troveremo di nuovo le tre variabili appena

Questo componente svolge la funzione di browser web, consentendo di visualizzare pagine internet nel nostro applicativo. Consente anche di consultare altri tipi di documenti, tra cui PDF, XML, immagini e altri: possibile considerarlo una panacea in situazioni in cui si vuole svolgere il pi velocemente un progetto, per limitazioni dell'API o per espressa volont

NOTA

RIFERIMENTI WEB

Per la creazione dell'account e per scaricare l'SDK, visitate il seguente percorso web:
http://developer.apple.co m/iphone

12

i Phone pr r am in g Luglio 2009/ o g31 mG

Componenti grafici e controllo UIWebView controllo UIWebView Componenti grafici e iPhone programming MOBILE

visuali e poterne cos modificarne stato e aspetto; l'iter pi semplice da seguire quando si inizia a lavorare con i file .xib. inoltre possibile invertire tale procedura lasciando che Interface Builder crei una classe partendo dall'interfaccia che avete creato, operazione che risulta di grande utilit quando si hanno molti oggetti visuali e non si desidera inserirli manualmente, oppure si deciso di realizzare prima il file xib della classe associata: questa ulteriore procedura verr spiegata, ma in un prossimo articolo.

NOTA

LA COMUNICAZIONE TRA GLI OGGETTI


In ambito object oriented la comunicazione tra due classi avviene principalmente attraverso l'invocazione di metodi: in Objective-C tale operazione prende il nome di invio di messaggi. Il design pattern, utilizzato per gestire gli eventi generati da un componente grafico, prende il nome di target-action; quando un utente interagisce con un componente lo induce a inviare un messaggio, chiamato action, a un altro oggetto, chiamato target, responsabile della sua gestione. possibile procedere in modi diversi, a seconda della classe da cui derivano tali componenti: bottoni (UIButton) e campi di input di testo (UITextField) ad esempio, possono richiamare, dei metodi presenti in una istanza di una classe utilizzando il qualificatore di tipo IBAction, eseguendo una procedura molto simile a quella utilizzata nel paragrafo precedente (IBOutlet). Per visualizzare l'elenco degli eventi che vengono lanciati da un componente visuale sufficiente premere il pulsante destro sul relativo oggetto presente nel Document Inspector di Interface Builder; con altri componenti si adopera la tecnica del delegate; una terza soluzione, disponibile per i componenti che discendono da UIControl, come UIButton, UITextField UIDate Picker e altri, consiste nell'utilizzare i selector, adoperando istruzioni Objective-C; per ora tratteremo solamente di IBAction.

IBOutlet viene utilizzato per notificare Interface Builder che tale variabile dovr essere utilizzata all'interno dell'interfaccia grafica, svolge quindi la funzione di connessione tra Controller e View, instaurando un collegamento bidirezionale.

IBOUTLET

Fig. 5: Dopo aver inserito le tre variabili all'interno del file .h, queste saranno visualizzate nell'Inspector della classe in Interface Builder

aggiunte alla classe. Baster trascinare il cerchietto, posizionato a destra, sul componente visuale che vogliamo associare. Tale operazione deve essere ripetuta per le tre variabili, associando a ognuna il rispettivo com-

LINTERFACE BUILDER ACTION - IBACTION


Fig. 6: La finestra semitrasparente

ponente visuale. Abbiamo cos completato la procedura necessaria per accedere, nel file ioProgrammoArt2ViewController.m, a tali oggetti

Con IBAction, acronimo di Interface Builder Action, identifichiamo quei metodi che Interface Builder mostrer all'interno della propria interfaccia grafica quando selezionata la classe in cui le abbiamo create, all'interno della sezione Class Actions del pannello Identity Inspector di IB (CTRL+4). Tali metodi rivestiranno il ruolo di gestori degli eventi lanciati dai
h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 32 /Luglio 2009

13

MOBILE Componenti controllo UIWebView iPhone programming grafici e Componenti grafici e controllo UIWebView

componenti visuali. IBAction viene utilizzato al posto del tipo di ritorno della funzione, infatti una macro, sinonimo di void (#define IBAction void), e per tale motivo tali metodi non restituiscono alcun valore. Non necessario creare una corrispondenza uno a uno tra un metodo IBAction e un evento richiamato da un singolo oggetto visuale, possibile associare tale codice a un numero qualsiasi di eventi lanciati da altrettanti componenti visivi. Nel nostro progetto abbiamo necessit di gestire la pressione del pulsante, che provveder in risposta a tale evento, a prelevare il valore del campo di input di testo e lo utilizzer per aprire la pagina relativa all'interno del webView, per tale motivo la chiamiamo gotoAddress:
//file ioProgrammoArt2ViewController.h #import <UIKit/UIKit.h> @interface ioProgrammoArt2ViewController : UIViewController { IBOutlet UIWebView *webView; IBOutlet UITextField *addressField; IBOutlet UIButton *goButton; } -(IBAction) gotoAddress; @end

A questo punto la domanda potrebbe sorgere spontanea: perch viene inserito in questa categoria, e non sotto Class Actions, cos come avviene nell'Identity Inspector? Questo perch la pressione del tasto effettua una chiamata al nostro metodo e, dal punto di vista della classe, tale chiamata significa ricevere un'azione (un messaggio) da parte di un oggetto esterno, nel nostro caso un componente visivo; non fatevi confondere, quindi, sono sinonimi. Trasciniamo il cerchietto sul pulsante, ci verr presentato un elenco di eventi gestiti dal bottone, selezioniamo Touch Up Inside, (evento generato quando l'utente solleva il dito dallo schermo dopo aver premuto il nostro pulsante). A questo punto non ci resta che scrivere il codice del metodo gotoAddress, ma prima sar necessario spiegare come si invoca un qualunque metodo appartenente a un oggetto Objective-C.

NOTA

VIEW-BASED APPLICATION

Abbiamo cos esposto, semplicemente inserendola nel file di interfaccia e identificandola con IBAction, tale funzione in Interface Builder. La funzione ci sar presentata nell'area chiamata Class Actions di Interface Builder, nella sezione omonima dell'Identity Inspector: ll passo successivo consiste nellassociazione della funzione alla pressione del pulsante che abbiamo creato, operazione identica a quella utilizzata con IBOutlet; baster quindi premere il tasto destro sulla riga corrispondente a File Owner per notare la disponibilit allinterno della categoria Received Actions

Fig. 8: L'effetto del trascinamento del cerchietto relativo all'IBAction sul pulsante

il progetto pi comodo per iniziare a programmare per iPhone/iPod Touch, infatti, fornisce una finestra contenuta all'interno di un controller, del quale viene fornita gi una classe di implementazione che consente al suo interno di gestire la parte di Controller, del pattern Model View Controller, responsabile, quindi, della gestione degli eventi e dello stato del nostro applicativo.

LA GESTIONE DEI METODI


Objective-C si differenzia principalmente da altri linguagg come Java, C#, Actionscript 3 poich utilizza un approccio differente per il passaggio di parametri. Per implementare un metodo necessario inserire a priori la sua signature, costituita da tipologia, tipo del valore restituito, nome, due punti, elenco e tipo dei parametri, nel file di interfaccia, come abbiamo fatto per -(IBAction) gotoAddress, terminata dal punto e virgola, poi scrivere il metodo completo di codice nel file di implementazione, quello con estensione .m. Un metodo che non accetta parametri verr scritto all'interno del file di interfaccia (.h) nel seguente modo:
//file .h - (void) nomeMetodo;

Fig. 7: La presenza dell'IBAction ora mostrata anche in questa finestra


h t t p : / / w w w. i o p r o g r a m m o . i t

e verr invocato tramite la sintassi:


i Phone pr 33 in g Luglio 2009/ o g r am mG

14

Componenti grafici e Componenti grafici e controlloiPhone programming controllo UIWebView UIWebView MOBILE

//file .m [oggetto nomeMetodo];

(alias) che assumer all'interno del corpo del metodo. Il calcolo del fattoriale di un numero avr questa signature:
//file .h -(double) FattorialeDi: (int) par1;

quindi sempre necessario racchiudere tra parentesi quadre sia l'oggetto che il nome del metodo invocato. Nel caso restituisca un intero:
//file .h - (int) nomeMetodo;

Che sar utilizzato in questo modo:


//file .m

sar possibile utilizzare tale risultato associandolo a una variabile nel seguente modo:
//file .m int variabile = [oggetto nomeMetodo];

int i = 5; double risultato = [oggetto FattorialeDi:i];

Con pi parametri, due in questo caso, si utilizza:


//file .h

NOTA

IBAction viene utilizzato per identificare un metodo di una classe, in modo da notificare Interface Builder che tale blocco di codice potr essere invocato da un componente grafico; allo scaternarsi di uno o pi eventi consente, inoltre, di esporre tale metodo all'interno di quelli forniti da un determinato oggetto visuale di IB. Ci avviene quando si preme il destro sulla relativa riga nel document inspector.

IBACTION

Il simbolo , che precede ogni metodo, indica che questo associato all'istanza, sostituendolo con un + lo si associa alla classe, verr quindi invocato nel seguente modo:
//file .m int variabile = [nomeClasse nomeMetodo];

-(void) nomeMetodo: (tipoPrimoParametro) par1 nomeSecondoParametro: (tipoSecondoParametro) par 2;

Se volessimo realizzare un metodo che effettua la somma tra due interi, si potrebbe utilizzare quindi la seguente sintassi:
//file .h

In questi articoli, a meno di precisa motivazione, avremo sempre la necessit di creare metodi per consentire la chiamata a una istanza di oggetto, utilizzeremo perci il -. Esiste un modo per associare un modificatore di accesso (public, protected, private), a un metodo? Non esplicitamente, per rendere un metodo privato si possono usare le categorie, ma non argomento di questo articolo: l'importante, per ora, essere consapevoli che qualunque metodo visibile all'esterno, da parte delle altre classi, a meno di utilizzare degli accorgimenti particolari, e che se non scriverete nell'implementazione della classe tutti i metodi dichiarati nell'interfaccia, verrete avvisati con il seguente warning:
warning: incomplete implementation of class 'ioProgrammoArt2ViewController' warning: method definition for 'nomeMetodo:' not found

-(void) Somma: (int) par1 con: (int) par 2;

Da invocare nel seguente modo:


//file .m int i = 100, j = 50; int result = [oggetto Somma:i con:j];

possibile effettuare chiamate in cascata come nel seguente esempio:


int result = [oggetto Somma:[oggetto Somma:i con; j] con: J];

Il cui risultato la somma (i+j)+j; il numero delle chiamate teoricamente illimitato, ma tale approccio ha il difetto di incrementare i rischi di

Nel caso un metodo accetti un parametro, si utilizza la seguente sintassi:


//file .h -(void) nomeMetodo: (tipoSingoloParametro) par1;

Fig. 9 Il sistema devidenziazione della parentesi quadra

Qui si nota la netta differenza con i linguaggi Object Oriented di nuova generazione; analizzandolo notiamo che i parametri seguono il simbolo dei due punti, poi si evince che non presente un nome per il primo parametro, questo ha solo associati il tipo di appartenenza e il nome

errori per la necessit di porre attenzione alla posizione delle parentesi quadre, sia aperte che chiuse: XCode ci viene in aiuto, infatti, posizionando il cursore su una parentesi, verr automaticamente evidenziata quella di chiusura corrispondente (o di apertura nel caso selezionassimo l'altra).
h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 34 /Luglio 2009

15

MOBILE Componenti controllo UIWebView iPhone programming grafici e Componenti grafici e controllo UIWebView

IL CODICE DI GOTOADDRESS
Ora che abbiamo spiegato come invocare un metodo generico, dovremo semplicemente prelevare il testo del campo addressField e passarlo come indirizzo a webView. Creiamo il metodo all'interno del file ioProgrammoArt2ViewController.m, nel codice identificato dal blocco @implementation @end:
//file ioProgrammoArt2ViewController.m #import "ioProgrammoArt2ViewController.h" @implementation ioProgrammoArt2ViewController -(IBAction) gotoAddress{ //Corpo del metodo } @end

getto che lo ha invocato, nel nostro caso il bottone chiamato goButton, per tale motivo disponibile una versione pi completa, che nel nostro esempio sar la seguente:
/file ioProgrammoArt2ViewController.h - (IBAction)gotoAddress:(id)sender;

Per prelevare il testo dal campo di input e aprire la pagina web (se esistente), baster utilizzare il seguente codice:
-(IBAction) gotoAddress{ //Preleviamo l'indirizzo NSString *address = [addressField text]; //Creiamo un indirizzo utilizzando tale stringa NSURL *url = [NSURL URLWithString:address]; //Creiamo una richiesta con tale indirizzo NSURLRequest *request = [NSURLRequest requestWithURL:url]; //Carichiamo la pagina utilizzando tale richiesta [webView loadRequest:request]; }

Abbiamo semplicemente aggiunto un parametro, di tipo id di nome sender che potremo utilizzare all'interno del blocco di codice del metodo per avere informazioni sull'oggetto che ha invocato tale comando. Questa funzionalit permette di condividere un IBAction tra un numero illimitato di oggetti, non solo di numero, ma anche di tipo, spetter a noi analizzare la variabile sender per capirne la tipologia. Un tipico esempio di utilizzo quello in cui abbiamo numerosi bottoni e desideriamo avere una sola zona di codice responsabile della loro gestione, utilizzando sender potremo identificare chi il chiamante e, se necessario, modificarlo opportunamente. Esiste un'ulteriore versione, la pi dettagliata disponibile, nella quale abbiamo modo anche di identificare l'evento che l'ha invocata:
/file ioProgrammoArt2ViewController.h - (IBAction)gotoAddress:(id)sender Event:(UIEvent*)event;;

LAUTORE
Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali e non su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certificatd Expert, EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multidediali con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it

Quando cliccheremo sul campo di testo si presenter la tipica tastiera e, successivamente, alla pressione del bottone verr caricata la pagina richiesta. possibile raggruppare le chiamate effettuate in una sola riga:
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:[addressField text]]]];

Come detto precedentemente, questa versione del codice risulta essere poco leggibile, a meno di non aver maturato una certa esperienza. Abbiamo completato la prima fase di questo semplice progetto. Ora, iniziamo a spiegare in dettaglio alcune caratteristiche dell'Objective-C e dell'interfacciamento con Builder, perch ci consentiranno di aggiungere ulteriori funzionalit nel prosieguo.

Tale variabile risulta utile se decidiamo di associre il metodo contemporaneamente a pi eventi, utilizzando, ad esempio, Touch Up Inside e Touch Down, eventi scatenati, in occasioni diverse, dal controllo afferente UITextInput. Il difetto di tali metodi dovuto al tipo del parametro utilizzato, id, il che rappresenta il tipo pi generale possibile, che, se da un punto di vista consente grande libert su quale componente utilizzare per invocare tale IBAction, dallaltro introduce alcune problematiche, principalmente riguardanti l'accesso ai campi e a i metodi dell'oggetto chiamante. Nel prossimo articolo a tale concetto dedicheremo particolare attenzione; lutilizzo consapevole di id consente di accedere alla estrema versatilit fornita dal linguaggio Objective-C.

ALLA PROSSIMA...
Creata una versione funzionante dell'applicativo, nel prossimo articolo del corso, lo modificheremo per fornire ulteriori funzionalit. Faremo cos conoscenza con altri argomenti correlati. Buona programmazione. Andrea Leganza
i Phone pr r am in g Luglio 2009/ o g35 mG

IBACTION E ID
Il precedente utilizzo di IBAction ha la limitazione di non fornire alcuna informazione sull'ogh t t p : / / w w w. i o p r o g r a m m o . i t

16

Componenti controllo UIWebView UIWebView CORSO MOBILE grafici e Componenti grafici e controlloiPhone programming

CREARE SOFTWARE PER LAPPLE IPHONE


N
CD WEB
iphonecorso4.zip
cdrom.ioprogrammo.it

IN QUESTA TERZA LEZIONE MODIFICHIAMO IL MINIBROWSER INTRODOTTO NEL PRECEDENTE ARTICOLO DELLA SERIE, INTRODUCENDO ALTRI CONCETTI FONDAMENTALI COME LA GESTIONE DEGLI ASPETTI GRAFICI E LUTILIZZO DEL COMPONENTE UIWEBVIEW

el precedente articolo abbiamo completato un'interfaccia minimale e abbiamo realizzato la logica necessaria per il suo corretto funzionamento, ora ci dedicheremo all'introduzione di ulteriori caratteristiche del linguaggio Objective-C; aggiungeremo un'animazione per monitorare il caricamento della pagina web richiesta.

IL TIPO ID
Nella seconda versione del metodo gotoAddress, introdotto nell'articolo precedente, che qui riproponiamo per semplicit,
//file ioProgrammoArt2ViewController.h (IBAction)gotoAddress:(id)sender;

(attribuendo ad esempio, il contenuto di una variabile String a una istanza di una classe creata estendendo un componente Swing) oppure si richiama un metodo inesistente (con parametri di tipo e/o numero diversi); in Objective-C se vengono rilevati dei conflitti o delle omissioni si riceve solamente un avviso, un warning in fase di compilazione, bisogna prendere consapevolezza che in tale contesto tutto incerto riguardo una variabile tranne quando si in esecuzione; solo gli errori sintattici, come parentesi e punti e virgola mancanti, interrompono il processo di compilazione. Associando, ad esempio, una variabile di tipo UITextField a una di tipo UIWebView, operazione concettualmente errata, ma perfettamente lecita in questo linguaggio:
UIWebView *oggettoBrowser; UITextField *oggettoCampoDiTesto= oggettoBrowser;

REQUISITI
Conoscenze richieste OOP Software Mac OS X 10.5 o superiore Impegno

Tempo di realizzazione

abbiamo utilizzato un parametro di tipo id, che ci ha consentito di rendere il pi generale possibile tale chiamata, in grado di essere invocata da qualunque componente visuale (ma non solo, e capirete il perch a breve): necessario discuterne approfonditamente, poich incontrerete id sia nell'API che nella documentazione; id, attenzione privo dell'asterisco, simbolo utilizzato quando ci si riferisce alle istanze delle classi, esiste perch Objective-C un linguaggio a tipizzazione dinamica, ci significa che solo a runtime, in esecuzione, il vostro software avr realmente modo di capire se una variabile appartiene ad un determinato tipo di dato, avr quindi una struttura derivante alla sua catena di ereditariet, partendo dalla root (in genere NSObject), e scendendo progressivamente, risultando perci corredato da precisi metodi e variabili; questo comportamento completamente diverso da quello che presente in JAVA e C#, linguaggi a tipizzazione statica, dove tale controllo avviene in fase di compilazione, e durante il quale si riceve un errore nel caso si assegni a un'istanza di una classe un tipo diverso da quello della sua catena di ereditariet

riceveremo il seguente avviso:


warning: 'initialization from distinct Objective-C type'

Per i metodi il discorso molto simile: il compilatore, se richiameremo un metodo inesistente, o sconosciuto al momento di compilazione come nel seguente caso:
//Definizione della variabile UILabel *oggettoLabel; //inizializzazione della variabile ... //Chiamata del metodo [oggettoLabel metodoInesistente];

segnaler la mancanza di informazioni a riguardo con il seguente avviso warning: 'UITextField' may not respond to 'metodoInesistente'
h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 48 /Agosto 2009

17

MOBILE Componenti controllo UIWebView iPhone programming grafici e Componenti grafici e controllo UIWebView

Notate il may utilizzato per denotare tale incertezza al momento della compilazione. Anche sbagliare la chiamata, invertendo semplicemente il tipo dei parametri, vi verr segnalato come un warning, e non un errore, il compilatore presuppone che sappiate cosa stiate facendo e si aspetta, a runtime, di trovare questo metodo con tale diversa signature. Il vostro software verr comunque compilato e avviato senza alcuna limitazione, il problema si presenter a runtime e il risultato sar uno, o pi crash, con il conseguente avvio del debugger (spesso molto vago sulla causa scatenante del crash). Attenzione quindi ai warning del compilatore, perch sono il primo segnale di un errore semantico e quindi di un possibile crash! Da quanto detto, si ha un'estrema libert (anche di sbagliare) nell'editor, ma si di conseguenza poco assistiti dal compilatore per prevenire molti tra gli errori pi comuni, che si erano risolti con i linguaggi di nuova generazione, per merito dei loro controlli preventivi. Per merito di questa dinamicit intrinseca al linguaggio, possibile utilizzare un placemark, un indicatore, per segnalare che un determinato oggetto al momento della compilazione non appartiene a una classe predefinita, perch non conosciuta a priori o non la si vuole specificare: in entrambi i casi si utilizza id come tipo della variabile. Nella documentazione relativa a IBAction viene utilizzato id per la signature, per indicarne la completa indipendenza dalla classe chiamante; id ha inoltre avuto il pregio di semplificare il processo di creazione dell'API, evitando di realizzare un metodo per ogni oggetto chiamante, poich potrebbe appartenere a una qualsiasi delle decine di classi disponibili, UIButton e UITextField e via discorrendo; in casa Apple ci si mantenuti il pi generale possibile, lasciando quindi al programmatore piena libert in che modo gestire tale chiamante. Id ha comunque delle limitazioni, a livello di usabilit: se si definisce una variabile di questo tipo non possibile avere un elenco preciso, utilizzando il sistema di Code Completition, delle funzioni e delle variabili appartenenti a tale oggetto, poich XCode non ha modo di sapere su che classe specifica si sta lavorando, riceveremo perci un elenco esaustivo di tutti i metodi disponibili per tutte le classi presenti nell'API: migliaia di metodi, variabili e costanti, una lista di scarsa utilit! Id pu generare confusione nel codice se l'utilizzo dell'oggetto associato non viene opportunamente descritto, oltre a incrementare il numero di possibili bug, poich si potrebbero chiamare metodi, e provare ad accedere a variabili, non presenti a runtime. Come possibile limitare l'elenco dei metodi, e dei campi, suggeh t t p : / / w w w. i o p r o g r a m m o . i t

riti da XCode quando siamo certi del tipo di variabile che id assumer a runtime? Nel nostro caso del tipo UIButton, un bottone, passato come parametro sender nel metodo IBAction. La soluzione molto semplice, basta creare una variabile del tipo desiderato e associarle il parametro sender; se pensiamo di utilizzarla un certo numero di volte, potremo utilizzare nel blocco di codice il seguente codice:
-(IBAction)gotoAddress:(id)sender { UIButton *mioBottone = sender; [mioBottone metodoUno]; [mioBottone metodoDue]; ... }

NOTA

Oppure utilizzare il casting:


-(IBAction)gotoAddress:(id)sender { [(UIButton *)sender metodoUno]; [(UIButton *)sender metodoDue]; ... }

Quest'ultima tecnica dovr essere utilizzata per ogni chiamata effettuata e/o per accedere ad un campo della classe, allo scopo di forzare l'editor a mostrare il corretto, e limitato, elenco di metodi e variabili; questa tecnica risulta per alquanto scomoda, degradando la leggibilit del codice, oltre ad incrementare il numero di caratteri da digitare. Se volessimo evitare di utilizzare id in questo contesto, potremmo semplicemente sostituirlo con il tipo corrispondente al componente grafico chiamante, UIButton nel nostro caso:
-(IBAction)gotoAddress:(UIButton *)sender { [sender metodoUno]; [sender metodoDue]; ... }

id svolge la funzione di identificatore generico di classe; una variabile di tale tipo pu appartenere a qualunque classe e la sua reale identit potr essere evidenziata solo a tempo di esecuzione; ha il difetto di non fornire informazioni se uno o pi metodi, o campi, a cui si accede esistono effettivamente a runtime, ci incrementa il rischio di bug e crash del sistema, se non viene utilizzato con accortezza.

ID

RIFERIMENTI WEB

In questo modo l'operazione di casting verr effettuata automaticamente. Applicando una qualunque di queste soluzioni, riceveremo suggerimenti coerenti con la classe che abbiamo specificato. L'unica limitazione dell'ultima soluzione che impedisce la condivisione di tale IBAction con quei componenti che non appartengono alla classe UIButton, rendendo quindi impossibile qualunque associazione diversa da quella tramite Interface Builder. Id pu essere utilizzato per qualunque variabile presente nella nostra classe, anche per gli IBOutlet; nell'articolo

Per la creazione dell'account e per scaricare l'SDK, visitate il seguente percorso web:
http://developer.apple.co m/iphone

18

i Phone pr r am in g Agosto 2009/ o g49 mG

Componenti grafici e Componenti grafici e controlloiPhone programming controllo UIWebView UIWebView MOBILE

precedente avevamo infatti detto che i tre IBOutlet utilizzati erano a tipizzazione forte, (strongly typed), cio limitavano i tipi di componenti visuali che potevamo associare, evitando possibili errori di collegamento in Interface Builder. Con l'introduzione di id, possiamo ora rendere pi dinamiche tali associazioni, baster modificare il codice nel seguente modo:
IBOutlet id webView; IBOutlet id addressField; IBOutlet id goButton;

NOTA

La necessit di utilizzo di id relativamente ridotta, conviene adoperarlo in situazioni di indeterminatezza a runtime; per identificare la reale classe di una sua istanza, basta invocare su di essa (pi correttamente inviare un messaggio) uno dei seguenti metodi:
- (BOOL)isKindOfClass: (Class)aClass

IDENTIFICARE LA REALE CLASSE DI ID

Questa versione, con id, chiamata anche a tipizzazione debole (weakly typed) quindi la pi generica tra quelle disponibili e consente di associare qualunque elemento visuale alle tre variabili: potremo quindi associare un campo di testo (visuale) al nostro webWiew (variabile) senza che il compilatore o Interface Builder segnali alcun problema; anche in questa situazione l'utilizzo indiscriminato di id, pi che altro privo di sufficiente conoscenza, rischia di incrementare il numero di possibili chiamate a metodi o accesso a campi inesistenti, con la generazione di numerosi crash. Per soddisfare la curiosit di alcuni sulla reale identit di id bisogna consultare la documentazione e si verr a conoscenza che un semplice puntatore a una struttura che contiene la classe, che verr utilizzata a runtime:
typedef struct objc_object { Class isa; } *id

altri dettagli; passando attraverso id siamo quindi giunti alla struttura che viene utilizzata per realizzare in Objective-C, utilizzando il linguaggio C, ogni classe che utilizziamo. Pu venire il dubbio, in alcuni contesti, se NSObject, poich la root delle classi che abbiamo fino ad ora utilizzato, intercambiabile con id; la risposta a tale domanda : dipende; id pu ospitare qualunque istanza appartenente a una classe anche non discendente da NSObject (come NSProxy e Protocol); una qualunque chiamata a un metodo, o accesso a un campo, di id non viene verificata, e segnalata in caso di inesistenza o incongruenza, in fase di compilazione, mentre se attribuiamo a una variabile il tipo NSObject, riceveremo un warning se invochiamo uno, o pi, metodi non esposti da tale classe di root; come stato detto, il sistema di completamento del testo per id risulta praticamente inutilizzabile, presentando tutti i metodi disponibili nell'API, nel caso di NSObject, invece, riceveremo quei metodi presenti in tale classe, senza per ottenere quelli forniti dalla catena di ereditariet: in entrambi i casi, per ottenere l'elenco pi coerente di metodi necessario effettuare il casting alla classe pi specifica desiderata, come stato mostrato negli esempi di gotoAddress.

DELEGATE
Con delegation, o message forwarding (conosciuto anche con i nomi di consultation e aggregation), si intende quella procedura che ha come risultato l'indirizzamento degli eventi che una classe genera verso un'altra, presente al proprio interno come una variabile, e che viene configurata per gestirli opportunamente; tale nome usato per descrivere un design pattern, descritto in ingegneria del software, che ha proprio questo comportamento; con delegate si intende una propriet della classe, identificata con una variabile di tipo id, che viene utilizzata per realizzare la delegation. Non tutte le classi forniscono tale variabile, nel nostro progetto disponibile per l'UITextField e UIWebView, ovviamente la documentazione la fonte migliore per determinare se la supportano. Quando utilizzare il delegate? Come abbiamo detto precedentemente, in questo contesto le classi svolgono la funzione di Controller dei componenti visuali, a volte per non necessario, o non si desidera, creare una classe che realizzi il Controller per ognuno di questi elementi, ma si vuole, comunque, gestire una certa famiglia di eventi inviati da tali istanze; un tipico esempio quello della UITableView, una tabella visuale che mostra una serie di dati in sequenza; in genere si fa delegare tutta la sua gestione, insieme alla visualizzazione delle proprie informazioni e
h t t p : / / w w w. i o p r o g r a m m o . i t

Tale Class un puntatore a una struttura:


typedef struct objc_class *Class;

e
(BOOL)isMemberOf Class:(Class)aClass;

definita nel seguente modo:


struct objc_class { struct objc_class *isa; struct objc_class *super_class; const char *name; long version; long info; long instance_size; struct objc_ivar_list *ivars; struct objc_method_list **methodLists; struct objc_cache *cache; struct objc_protocol_list *protocols; };

questi due consentono di ottenere un riscontro immediato, fornendoci le informazioni necessarie per effettuare un casting allo scopo di scegliere operazioni coerenti con la reale identit dell'istanza.

nella quale troviamo tutte le informazioni che rappresenteranno lo stato dell'istanza che id conterr, tra cui metodi, variabili, protocolli, e

i Ph on e p r o g r a m m ing

G 50 /Agosto 2009

19

MOBILE Componenti controllo UIWebView iPhone programming grafici e Componenti grafici e controllo UIWebView

all'interazione dell'utente, al UIViewController che la contiene (utilizzando il protocollo UITable ViewDataSource di cui parleremo nel prossimo paragrafo). possibile impostare il delegate in due modi: il primo consiste nell'utilizzare Interface Builder, premere il destro del mouse sulla riga corrispondente al componente visuale del Document Inspector, noterete una riga con tale nome, e potrete associare tale propriet a un oggetto semplicemente trascinando il relativo cerchietto, come stato fatto per gli IBOutlet L'altro consiste nell'utilizzare un comando in linguaggio Objective-C:
miaIstanza.delegate = oggettoDelegato;

Source, citato nel paragrafo precedente quando parlavamo della UITableView associata a un UIViewController, questo espone i seguenti metodi ( solo un estratto):
@required - (NSInteger)tableView:(UITableView *)table numberOfRowsInSection:(NSInteger)section; - (UITableViewCell *)tableView:(UITableView *) tableView cellForRowAtIndexPath:(NSIndexPath *) indexPath; @optional - (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView; - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section; - (NSString *)tableView:(UITableView *)tableView titleForFooterInSection:(NSInteger)section; ...

NOTA

DELEGATE

Il risultato identico, l'unica differenza che nel secondo caso si ha modo di controllare quando inizializzare tale associazione, mentre nel primo avviene in un non precisato momento durante la trasformazione del file xib, che, come detto precedentemente, un file XML, nel codice Objec tive-C necessario per realizzare linterfaccia grafica da inserire nell'eseguibile. Abbiamo cos impostato che l'oggetto miaIstanza delega la sua gestione a oggettoDelegato. Impostare il delegate di un'istanza di una classe non per sufficiente affinch tale delegato possa intercettare ed elaborare gli eventi: necessario che tale classe implementi i metodi che verranno richiesti, diventi cio conforme ad un protocollo. Il nome di tale protocollo, corredato del relativo elenco di metodi, lo si ritrova nella documentazione associata al parametro delegate della classe sulla quale volete applicare la delegation. Per semplificare questa procedura disponibile, nel caso della suddetta UITableView, una componente visuale chiamata UITableViewController che consiste proprio in un UIViewController e una UITableView al suo interno, gi configurato in questo modo.

I PROTOCOLLI
Il concetto di protocollo suoner familiare a molti utilizzatori di linguaggi object-oriented di nuova generazione, perch in Java, come in C#, vengono chiamati Interfacce, mentre Classi Astratte in C++. Diventare conforme (a volte si utilizza il termine adattarsi) a un protocollo, nella pratica significa garantire che una determinata classe utilizza, ha quindi implementato, tutti i metodi dichiarati nel protocollo rispettando i nomi, il tipo e il numero dei parametri utilizzati; il comportamento interno dei metodi non interessa al protocollo, demandato al programmatore. Un esempio quello dell'UITable ViewData
h t t p : / / w w w. i o p r o g r a m m o . i t

come si pu notare immediatamente, da una prima analisi di tale codice, non viene inserito alcun blocco di comandi Objective-C all'interno dei metodi, ma troviamo semplicemente il nome e i parametri. Ogni protocollo da considerare al pari di un patto con il quale si garantisce che si rispetta un certo standard, almeno riguardo i nomi ed i parametri. I protocolli vengono suddivisi in due categorie: formali ed informali: con i primi si indicato quelli che, utilizzando solo la keyword @required, obbligano l'implementazione di tutti i propri metodi, mentre con i secondi si fornisce libera scelta su quale sottoinsieme di metodi realizzare, indicazione che avviene utilizzando la keyword @optional; dichiarando la conformit di una nostra classe a uno o pi protocolli formali richieder un controllo da parte del compilatore in fase di compilazione, al contrario di quelli informali, che vengono completamente ignorati. Le interfacce utilizzate in Java e/o C#, sono quindi comparabili ai protocolli formali. Ma perch in ObjectiveC possibile avere tale libert di scelta? Merito del fatto che, come stato detto precedentemente, un linguaggio a tipizzazione dinamica: anche il controllo se una classe rispetta un determinato protocollo, avviene a runtime, utilizzando un procedimento chiamato reflection, tramite il quale la classe viene interrogata (o pi correttamente si interroga) se ha certe caratteristiche. Da quando detto potremmo implementare tutti i metodi necessari per il protocollo UITableViewData Source senza dichiararlo esplicitamente, tanto, se vengono rilevati in fase di esecuzione verranno comunque invocati. Analizzando i metodi presenti nella classe di root, NSObject, troviamo il seguente metodo:
- (BOOL)conformsToProtocol:(Protocol *)aProtocol

Con delegate si identifica quella istanza di una classe responsabile della gestione degli eventi lanciati da un altro oggetto; risulta utile quando si realizzano interfacce con numerosi componenti visuali e si preferisce centralizzare il codice all'interno di un numero pi ristretto di classi.

NOTA

DEASOCCIARE IL DELEGATE

Quando si imposta manualmente il delegate con oggetti che lanciano eventi asincroni, come pu accadere nel nostro UIWebView, si rischia di incorrere in chash casuali se non si deallocano correttamente le risorse associate se si rimuove il controllore; tale evento accade frequentemente quando si utilizza un controller UINavigationController; conviene quindi rimuovere questo legame nel metodo dealloc o appena possibile;

20

i Phone pr r am in g Agosto 2009/ o g51 mG

Componenti grafici e Componenti grafici e controlloiPhone programming controllo UIWebView UIWebView MOBILE

NOTA

IBAction viene utilizzato per identificare un metodo di una classe, in modo da notificare Interface Builder che tale blocco di codice potr essere invocato da un componente grafico; allo scaternarsi di uno o pi eventi consente, inoltre, di esporre tale metodo all'interno di quelli forniti da un determinato oggetto visuale di IB. Ci avviene quando si preme il destro sulla relativa riga nel document inspector.

IBACTION

il cui scopo proprio quello di fornire una risposta certa se si rispetta un determinato protocollo; tale metodo viene invocato automaticamente numerose volte durante l'esecuzione del vostro applicativo in maniera completamente trasparente. Dichiarare esplicitamente che una classe conforme a uno, o pi, protocolli permette sia di sapere quali metodi sono obbligatori (nel caso dei formali), perch anche una sola omissione viene segnalata dal compilatore, ma soprattutto una pratica di buona programmazione (valida sia per i formali che per gli informali), rendendo palesi le proprie intenzioni anche ad altri sviluppatori che potrebbero analizzare in futuro il vostro codice. Per dichiarare se una classe conforme a un protocollo, utilizziamo ad esempio quello mostrato precedentemente, UITable ViewDataSource, sufficiente inserire il suo identificativo, racchiuso tra i due simboli minore e maggiore, all'interno del file di interfaccia della classe, successivamente alla classe da cui deriva la nostra:
@interface ioProgrammoArt2ViewController : UIViewController <UITableViewDataSource>

prelevando le signature dalla documentazione del protocollo, e incolla, all'interno di XCode.

IL CARICAMENTO DELLA PAGINA


Nel nostro progetto decidiamo di monitorare il caricamento della pagina web mostrando, quando questo processo non ancora terminato, un'animazione; per semplicit utilizziamo un UIActivityIndicatorView, un componente visuale che mostra un'animazione infinita (ma comunque interrrompibile), il tipico effetto di una spirale rotante, familiare agli utenti Mac. Abilitiamo, tramite la finestra Attributes Inspector (CTRL+1) di IB, la voce Hide When Stopped, in tal modo nasconderemo automaticamente tale componente quando interromperemo la sua l'animazione. Per accedere nella nostra classe ioProgrammoArt2ViewController.m a tale componente visuale dobbiamo aggiungere, effettuando anche il collegamento in IB, un nuovo IBOutlet all'interno di ioProgrammoArt2ViewController.h:
//file ioProgrammoArt2ViewController.h #import <UIKit/UIKit.h> @interface ioProgrammoArt2ViewController : UIViewController { IBOutlet UIWebView *webView; IBOutlet UITextField *addressField; IBOutlet UIButton *goButton; IBOutlet UIActivityIndicatorView *loadMonitor; } @end

NOTA

Ogni volta che si utilizza un IBOutlet, dichiarandolo all'interno del file .h e associandolo ad un componente visivo tramite Interface Builder, necessario rilasciare la memoria che gli viene riservata a runtime; tale operazione viene effettuata invocando il metodo release sul relativo IBOutlet all'interno del metodo dealloc.

DEASOCCIARE IBOUTLET

Per aggiungere ulteriori protocolli basta semplicemente separarli con una virgola. UITable ViewDataSource un protocollo che dichiara sia metodi obbligatori (due) che opzional (numerosi), saremo quindi obbligati a implementare solo i primi due, numberOfRowsInSect ion e cellForRowAtIndexPath. La frase con cui stato terminato il paragrafo precedente quindi parzialmente vera, necessario dichiarare esplicitamente di essere conformi a uno o pi protocolli formali, ma buona pratica farlo anche per quelli informali. importante disaccoppiare il delegate quando rimuoviamo le risorse associate a un UIViewController che contiene la nostra classe, tale operazione si effettua semplicemente impostandolo al valore nil:
miaIstanza.delegate = nil;

Si tratta di un'operazione necessaria, poich, se vengono inviati messaggi al delegate quando questo non pi disponibile, in genere dopo essere stato deallocato, si verificher un crash a runtime; un errore comune quello che si presenta quando si analizzano i dati dell'accelerometro e non si effettua tale procedura di rilascio. Concludiamo questo argomento suggerendo di porre una certa attenzione nella fase di scrittura della signature dei metodi, poich una qualunque differenza con la versione attesa non permetter a questi di essere invocati quando necessario: la procedura pi sicura consiste nell'effettuare una operazione di copia,

Ricordate di associarlo in Interface Builder, altrimenti non si avr modo di accedervi nei metodi che realizzeremo. Per mostrare l'utilit della delegation, invece di realizzare una classe da associare alla UIWebView, andremo a impostare come delegate di questo componente visuale il nostro UIViewController; come precisato precedentemente, conveniente impostare il protocollo, anche se non espressamente richiesto; consultando la documentazione veniamo a conoscenza che il suo delegate del tipo id<UIWebViewDelegate>, dovremo quindi essere conformi al protocollo UIWebViewDelegate:
@protocol UIWebViewDelegate <NSObject> @optional - (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType: (UIWebViewNavigation Type)navigationType; - (void)webViewDidStartLoad:(UIWebView *)webView; - (void)webViewDidFinishLoad:(UIWebView *)webView;

i Ph on e p r o g r a m m ing

G 52 /Agosto 2009

h t t p : / / w w w. i o p r o g r a m m o . i t

21

MOBILE Componenti controllo UIWebView iPhone programming grafici e Componenti grafici e controllo UIWebView

do la signature dalla documentazione, nel file ioProgrammoArt2ViewController.m


//file ioProgrammoArt2ViewController.m #import "ioProgrammoArt2ViewController.h" @implementation ioProgrammoArt2ViewController -(IBAction) gotoAddress { [webView loadRequest:[NSURLRequest requestWithURL :[NSURLURLWithString:[addressField text]]]]; } - (void)webViewDidStartLoad:(UIWebView *)webView { // } - (void)webViewDidFinishLoad:(UIWebView *)webView { // } @end

LAUTORE
Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali e non su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certificatd Expert, EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multidediali con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it

Baster infine avviare l'animazione in un metodo e interromperla nell'altro: ci reso possibile nella classe UIActivityIndicatorView invocando i due metodi, privi di parametri, startAnimating e stopAnimating.
- (void)webViewDidStartLoad:(UIWebView *)webView {

Fig. 1: Laggiunta del componente UIWebView alla nostra schermata

[loadMonitor startAnimating]; } - (void)webViewDidFinishLoad:(UIWebView *)webView {

- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error; @end }

[loadMonitor stopAnimating];

I metodi disponibili sono tutti opzionali, provvediamo quindi a implementare solo webView DidStartLoad, che viene lanciato quando ha inizio il caricamento di una pagina, e webViewDid FinishLoad che notifica il suo completamento. Prima dobbiamo impostare il delegate tramite Interface Builder, operazione che come precedentemente spiegato, avviene trascinando il relativo cerchietto verso il File Owner, Ora dobbiamo passare a XCode e modificare prima il file ioProgrammoArt2ViewController.h
//file ioProgrammoArt2ViewController.h #import <UIKit/UIKit.h> @interface ioProgrammoArt2ViewController : UIViewController < UIWebViewDelegate>{ IBOutlet UIWebView *webView; IBOutlet UITextField *addressField; IBOutlet UIButton *goButton; IBOutlet UIActivityIndicatorView *loadMonitor; } @end

CONCLUSIONI
Termina cos questa serie di articoli in cui abbiamo esposto alcuni dei concetti che creano maggiori problemi quando si realizza per la prima volta un applicativo per iPhone. Nei prossimi mesi mostreremo il funzionamento di altri componenti visuali, e forniremo ulteriori nozioni sul linguaggio Objective-C. Buona programmazione. Andrea Leganza

aggiungendo <UIWebViewDelegate>, e successivamente creando i due metodi richiesti, copianh t t p : / / w w w. i o p r o g r a m m o . i t

Fig. 2: Come si imposta il delegate

22

i Phone pr r am in g Agosto 2009/ o g53 mG

iPhone: gestire le tabelle per rappresentare ile tabelle per rappresentare i dati iPhone: gestire dati iPhone programming CORSO MOBILE

REALIZZIAMO UNA AGENDA PER IPHONE


DA QUESTO NUMERO INIZIA UNA TRATTAZIONE CHE UN PO IL CUORE DI QUASI OGNI APPLICAZIONE IPHONE. STIAMO PARLANDO DELLE TABELLE. VEDREMO COME SI CREANO, QUALI TIPOLOGIE UTILIZZARE A SECONDA DEL CONTESTO, E COME GESTIRLE AL MEGLIO

U
CD WEB
ioProgrammoArt4.zip
cdrom.ioprogrammo.it

no dei componenti pi utili e utilizzati nello sviluppo di applicativi su 'iPhone, quello identificato dalla classe UITable View, una tabella a scrolling verticale, che consente di mostrare un elenco ordinato, e opzionalmente modificabile da parte dell'utente, di informazioni generalmente coerenti. Tale componente praticamente onnipresente in ogni applicativo, possibile trovarlo in numerose personalizzazioni all'interno dell'elenco dei brani audio presenti nel vostro dispositivo, in quello dei film, in quello della posta elettronica, fino all'applicativo dei contatti. Lo utilizzeremo prima nella versione fornita, creando un progetto omonimo utilizzando il wizard e provvederemo poi a custo-

mizzarlo progressivamente per realizzare una nostra versione graficamente pi accattivante. Il progetto che andremo a realizzare sar una todo list, un normale elenco di azioni da fare, procedura che ci consentir di analizzare in dettaglio la struttura di tale componente visuale, oltre che a prendere confidenza con le operazioni da effettuare per gestire l'interazione dell'utente e la relativa modifica, sia sotto l'aspetto grafico, che semantico delle informazioni che andremo a presentare visivamente: tutto ci ci dar modo di introdurre nuovi tipi di dato e di conoscere tecniche e classi fornite da Objective-C.

TABELLE CON UITABLEVIEW


Come viene realizzata una tabella nella programmazione iPhone, all'interno dell'SDK? La classe a cui fare riferimento quella chiamata UITableView, le cui istanze ereditano a loro volta dalla classe da cui questa discende direttamente: UIScrollView, tale componente consente di effettuare lo scrolling di contenuti, ed utilizzato normalmente quando la dimensione di

REQUISITI
Conoscenze richieste OOP Software Mac OS X 10.5 o superiore Impegno

Tempo di realizzazione

Fig. 1: L'interfaccia che realizzeremo al termine della serie di articoli incentrati sulla tabella

Fig. 2: La catena di ereditariet della classe UITable


h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 48 /Settembre 2009

23

iPhone: gestire i tabelle per rappresentare i dati MOBILE iPhone: gestire le tabelle per rappresentarele dati iPhone programming

tali informazioni superiore a quella concessa dallo schermo dell'iPhone/iPod (320x480 pixel massimo, ottenibili quando non si utilizzano componenti visuali di navigazione, come UITabBarController e UINavigation Controller); risulta molto utile per mostrare a video immagini di dimensioni relativamente grandi, oppure per fornire un semplice sistema di navigazione tra schede contenenti informazioni diverse; per mostrare la posizione in cui ci troviamo, vengono utilizzate due barre di colore normalmente nero, identiche a quelle utilizzate nel componente utilizzato nel precedente articolo, l'UIWebView. Rispetto a UIScrollView, UITableView aggiunge alcuni metodi e variabili specifiche per consentire l'interazione da parte dell'utente e per ottimizzare la visualizzazione e la persona-lizzazione, sia estetica che semantica, delle singole righe che vengono utilizzate per visualizzare le informazioni fornite; proprio le righe che abbiamo appena citato, in questo contesto prendono il nome di celle (sono per tale motivo istanze della classe UITableViewCell) e saranno oggetto principale di questa serie di articoli; tratteremo comunque in un numero successivo della rivista anche l'UIScrollView. Poich la classe UITableView discende, attraverso la classe padre, da UIView, ne eredita le caratteristiche, ed necessario, per tale motivo, inserire questo componente sempre all'interno di un Controller o di una classe da esso derivata; oltre a effettuare tale pratica manualmente, utilizzando un qualsiasi Controller, inserendo la UITableView al suo interno, e gestendo la connessione tramite codice Objective-C (IBOutlet), possibile velocizzare il tutto utilizzando un componente realizzato ad hoc, discendente di UIViewController e chiamato UITableViewController. Tale oggetto ha gi al suo interno una variabile, una IBOutlet (tenete a mente le nozioni di IBOutlet e IBAction perch le ritroveremo sempre d'ora in poi, sarebbe opportuno andare a riconsultare gli articoli precedenti nel caso riteniate di non averli metabolizzati) che ospiter la nostra tabella e per tale motivo risulta la strada pi veloce da seguire quando si utilizza Interface Builder, evitandoci la creazione di una IBOutlet apposita. Inoltre, essendo alla sua radice una UIView, permette di essere personalizzata con componenti visivi, quali testi, immagini, bottoni etc. Se avete modo di analizzare con attenzione i diversi applicativi disponibili nativamente con l'iPhone /iPod Touch, troverete decine di varianti di questo componente, per non parlare delle versioni realizzate dai programmatori che pubblicano sull'Apple Store! probabilmente il componente pi personalizzato e utilizzato fornito dall'SDK.
h t t p : / / w w w. i o p r o g r a m m o . i t

LA CREAZIONE DEL PROGETTO


Per semplificare questo tutorial, utilizzeremo una delle voci preesistenti presenti nel wizard dei nuovi progetti; selezioneremo per tale motivo un nuovo progetto della categoria Navigation Based Application, che ha il pregio di essere gi configurato con una UITableView, inserita all'interno di un UITableViewController. In questa circostanza non utilizzeremo la nuova funzionalit introdotta con l'SDK 3, chiamata Core Data, e, nel caso la trovaste selezionata ( attivabile utilizzando il checkbox che appare quando si seleziona questo tipo di progetto, ma solo se si ha aggiornato l'SDK alla versione 3) deselezionatela; per chi fosse curioso su cosa Core Data, la risposta semplice, una nuova funzionalit che consente di gestire il Model senza dover scrivere comandi SQL per interfacciarsi con il database fornito dall'SDK: SQLITE: fornisce un livello di astrazione, che semplifica notevolmente l'utilizzo di questo database, esponendo metodi e metodologie il cui scopo quello di ridurre al minimo l'impegno necessario per gestire la memorizzazione delle informazioni, che possono essere non temporanee (il cui tempo di vita quindi superiore al singolo utilizzo dell'applicativo) e/o quando queste sono altamente strutturate (si pensi alla memorizzazione di informazioni di utenti se si realizzasse un sistema di interfacciamento con un social network): proprio per il loro particolare utilizzo dedicheremo in futuro opportuno spazio. Quando avremo completato la fase di creazione del progetto troveremo alcuni file al suo interno, due xib (MainWindow.xib e RootViewController .xib) e alcune classi, tra le quali quella che ci interessa prende il nome di RootViewController .h/.m. Cerchiamo ora di capire cosa stato creato dal wizard, ma in particolar modo come stato realizzato.La procedura automatica ha creato un file xib che contiene (e conterr) tutti i nostri componenti visuali, una superview, chiamata MainWindow, al cui interno viene inserito automaticamente il componente chiamato RootView controller.xib; questo file grafico associato alla classe omonima che un'istanza della classe UITableViewController: aprendo tale file all'interno di Interface Builder verr visualizzata una tabella perfettamente funzionante, baster infatti eseguire il progetto nel simulatore per effettuare lo scrolling utilizzando il dito. Anche se all'interno di Interface Builder, in questo caso, sono mostrati dei contenuti, delle citt della California, non bisogna preoccuparsi di eliminare tali voci, andandole a cercare all'interno del codice del progetto, poich queste sono semplicemente degli indicatori visivi di come potrebbe

NOTA

IPHONE 3GS

Il nuovo iPhone 3GS monta un processore di circa 600MHz, 256MB di RAM, OpenGL ES 2, contro il precedente di circa 412MHz, 128MB di RAM e OpenGl ES 1.1, l'iPod Touch 2G si pone nel mezzo con circa 533MHz e 128MB di RAM.

24

Phone pr r am in g Settembre i2009/o g49 m G

iPhone: gestire le tabelle per rappresentare ile tabelle per rappresentare i dati iPhone: gestire dati iPhone programming MOBILE

apparire la vostra tabella; non bisogna per tenere molto in considerazione tale anteprima, poich non rifletter pi la reale visualizzazione della vostra tabella quando andrete a effettuare anche minimi cambiamenti al suo codice; se non effettuerete invece personalizzazioni utilizzando Objective-C, potrete effettuare modifiche utilizzando l'inspector (la finestra che contiene tutte le propriet del componente visuale) per ogni modifica avr un riscontro visivo immediato, anche se non sempre fedele. Avviare il progetto e testarlo, sia nel simulatore che sul dispositivo, sar quindi l'unico modo per avere un riscontro reale dell'effettivo comportamento, e dell'aspetto reale, della tabella. Purtroppo, nonostante la relativa semplicit del progetto, perderemo di vista completamente Interface Builder, poich non consente di effettuare modifiche estetiche (se non in maniera molto limitata) e strutturali alla tabella: utilizzeremo d'ora in poi solamente codice Objective-C.

valori e comandi utilizzati per configurare diversi applicativi e impostazioni. La modalit grouped inoltre la pi adatta quando si raggiunge l'ultimo livello di dettaglio delle informazioni, quello in cui si mostrano tutti i dati riguardanti una determinata voce. Decidere quale delle due tipologie scegliere, quindi un'operazione relativamente semplice, tenendo a mente la loro diversa funzione.

Fig. 4: La modalit di visualizzazione plain

NOTA IBAction viene utilizzato per identificare un metodo di una classe, in modo da notificare Interface Builder che tale blocco di codice potr essere invocato da un componente grafico; allo scaternarsi di uno o pi eventi consente, inoltre, di esporre tale metodo all'interno di quelli forniti da un determinato oggetto visuale di IB. Ci avviene quando si preme il destro sulla relativa riga nel document inspector.

IBACTION

LA STRUTTURA DELLA UITABLEVIEW IN MODALIT GROUPED


Fig. 3: Come sono strutturati i componenti visuali

LE RAPPRESENTAZIONI DI UITABLEVIEW
Esistono due tipi di rappresentazione grafica di una tabella standard: quella chiamata plain, che mostra un elenco semplice di righe, che d'ora in poi chiameremo con il nome utilizzato all'interno dell'SDK e della documentazione ufficiale, ovvero celle, e quella chiamata groupe, in cui insiemi di celle sono raggruppati in gruppi identificati da una stringa di testo. La prima modalit la pi semplice, e si presta a essere utilizzata quando non si desidera fornire una differenziazione tra diverse tipologie di informazioni mostrate a schermo, come per un elenco di dati appartenenti a una stessa tipologia, utenti di uno stesso social network a esempio, mentre la seconda diventa la soluzione logicamente, ma soprattutto graficamente, pi opportuna quando mostriamo a schermo diverse tipologie di informazioni, elencando prodotti di un negozio ad esempio. La seconda modalit utilizzata anche all'interno del menu settings dell'iPhone, dove nella stessa schermata troviamo

La modalit grouped merita un ulteriore approfondimento, poich presenta una struttura particolare, la cui personalizzazione consente di variare il suo aspetto di default. Nell'immagine di Fig.5 sono mostrati tre gruppi con realtive intestazioni, Remote Host: www.apple. com, Access To Internet Hots e Access To Local Bonjour Hosts, ognuno di questi ha una sola cella associata: in questo esempio possiamo chiaramente notare che viene creata una spaziatura sopra il gruppo chiamata padding (superiore),

Fig. 5: La modalit di visualizzazione grouped

i Ph on e p r o g r a m m ing

G 50 /Settembre 2009

h t t p : / / w w w. i o p r o g r a m m o . i t

25

iPhone: gestire i tabelle per rappresentare i dati MOBILE iPhone: gestire le tabelle per rappresentarele dati iPhone programming

poi troviamo uno spazio utilizzato per contenete l'header, l'intestazione del gruppo, a cui segue la cella vera e propria con i suoi contenuti, seguita da un footer, un'area in cui possibile inserire testo ad esempio e infine da un ulteriore padding (inferiore). possibile personalizzate tutte queste sezioni, ma solamente utilizzando codice Objective-C, si inizia a comprendere come Interface Builder perda di utilit anche nei contesti pi semplici rendendo necessario familiarizzare con il linguaggio object oriented il pi velocemente possibile.

Come detto in precedenza, quando si presenta l'insieme di informazioni relative all'ultimo livello, consigliabile utilizzare una struttura di tipo grouped oppure, nel caso si desiderasse una struttura pi complessa, una UIView (UIScroll View o simili) opportunamente strutturata.

GLI INDICATORI DI DETTAGLIO


Quando si realizza una struttura multilivello necessario informare visivamente l'utente se esiste un successivo livello di navigazione cliccando su una determinata cella oppure no. Per tale motivo sono disponibili due icone per rappresentare la disponibilit di un maggiore dettaglio per l'informazione selezionata, e che vengono posizionate all'estrema destra delle singole celle. Una di queste, ottenibile impostando il tipo di cella come un UITableViewCellAccessory Disclo sure-Indicator, e la cui immagine una freccia verso destra, simile al simbolo di maggiore di colore grigio (>) chiamata Dislosure Indicator, informa che il prossimo livello generalmente conterr un'altra tabella (con maggiore dettaglio), e un'altra, UITableViewCellAccessory DetailDisclosureButton, chiamata Detail Disclo sure Button, identificata da un cerchio celeste con lo stesso simbolo di >, ma di colore bianco, che in questo caso indica che il prossimo livello dovrebbe essere quello finale; nessun controllo automatico vieta di agire in maniera diversa, realizzando diverse strutture con questi simboli, ma altamente sconsigliato per evitare di non andare contro le regole di usabilit dettate dalla ditta di Cupertino che potrebbero impedire l'accettazione del vostro software per la vendita sull'Apple Store ( capitato a numerosi utenti): il consiglio quindi quello di essere fedeli al comportamento standard quando si utilizzano simboli e i componenti forniti dall'SDK. Ovviamente l'assenza di uno o dell'altro simbolo implica la mancanza di alcuna schermata successiva alla presente, per tale motivo dimostrazione di scarsa attenzione non utilizzarli quando

CONSULTAZIONE MULTILIVELLO
Quando si fornisce una lista di informazioni presentate all'interno di una UITableView, generalmente si desidera rendere disponibile un sistema di consultazione multilivello, dove viene mostrato un primo livello, nel quale viene visualizzato un elenco complessivo, che l'utente potr scorrere, e generalmente un secondo livello di accesso a tali dati, nel quale si mostrano i dettagli relativi alla cella selezionata nel livello precedente: lo stesso principio applicato nella navigazione delle pagine web utilizzando i menu. In realt il numero di tali livelli virtualmente illimitato, ma superare di numero il valore tre/quattro crea problemi di orientamento all'utente che utilizza tale sistema di navigazione, molti utilizzatori non saranno in grado di ricordate percorsi superiori a tre interazioni e potrebbero trovare poco intuitivo tale approccio: il numero di utilizzi di un'informazione presente in un determinato livello si riduce in maniera inversamente proporzionale al numero di tale livello: potreste scoprire con il tempo che, tanta fatica fatta per realizzare una determinata UIView, non apprezzato poich troppo in profondit!

NOTA

La tabella di tipo plain si presta per elenchi generici, mentre quella grouped per rappresentare le schermate di dettaglio, oppure quando le informazioni mostrate sono raggruppate per una o pi caratteristiche comuni.

PLAIN E GROUPED

Fig. 6: Le diversa gestione degli spazi presente quando una tabella del tipo grouped

Fig. 7: Un esempio di navigazione a tre livelli. Le informazioni sono sempre pi dettagliate con il progredire del numero di livello

h t t p : / / w w w. i o p r o g r a m m o . i t

26

Phone pr r am in g Settembre i2009/o g51 m G

iPhone: gestire le tabelle per rappresentare ile tabelle per rappresentare i dati iPhone: gestire dati iPhone programming MOBILE

necessario. inoltre disponibile una terza icona, generalmente utilizzata quando si effettuano selezioni singole o multiple, quando si scelgono, ad esempio, diversi prodotti: il simbolo di spunta (UITableViewCellAccessoryCheckmark) chiamato Check mark. Se questi sono i simboli pi utilizzati, nessuno vieta, comunque, di personalizzarli a proprio piacimento, inserendo immagini create con il proprio programma di illustrazione preferita, renderete pi accattivante e personale la vostra applicazione, evitando di farla sembrare troppo anonima e poco di impatto; conviene comunque utilizzare simboli di immediata comprensione per non confondere l'utente, evitando, ad esempio, di utilizzare frecce con verso illogico o immagini poco intuitive.

DATA SOURCE E DELEGATE


Ogni tabella, istanza di UITableView, ha bisogno di essere associata a due altre istanze, di qualunque classe; infatti, controllando l'API, noterete che utilizzato in entrambi i casi l'identificatore id, sinonomio di qualunque oggetto; una istanza sar responsabile di fornirle i dati da mostrare a schermo, e viene identificata dal Data Source (Model del pattern MVC), e un'altra provveder a gestire aspetto e comportamento (View e Controller del pattern MVC): non necessario che queste due istanze siano diverse, infatti, in progetti non molto strutturati, si preferisce farli combaciare; tale comportamento viene inoltre seguito dal wizard stesso, che infatti ha provveduto automaticamente ad associare le due propriet alla nostra istanza di RootViewController: baster andare in Interface Builder, premendo il tasto destro del mouse sul File Owner per verificare tale affermazione. Da parte sua la UITableView creata riveste il ruolo per il RootViewController di View e di propriet tableView. Si realizzato quindi un ciclo di dipendenze. UITableView interrogher i due delegati per effettuare tutte le operazioni, sia automatiche che scatenate dall'interazione dell'utente e su come presentare il proprio aspetto e quello delle celle. Data Source utilizza il protocollo UITableViewDataSource, mentre il delegato il UITableViewDelegate; nel primo caso si richiede al delegato, in questo caso il file RootViewController.h/.m, di essere interme- diario tra la tabella e il model, fornendo su richiesta le informazioni necessarie e consentendo il loro inserimento, modifica e/o cancellazione. UITableViewDelegate, invece, responsabile di fornire informazioni alla tabella su quale l'aspetto delle celle e di come gestire l'interazione con l'utente, ed anche in questo caso coincide con RootViewController.h/.m; i metodi disponibili sono decine e la documentazione disponibile la fonte pi adatta.

TIPOLOGIE DI CELLE
NOTA

Qualunque utilizzo di un applicativo iPhone pensato o come lo sfogliare un libro, muovendosi parallelamente, oppure come navigare all'interno di un sito web, ottenendo informazioni sempre pi dettagliate man mano che si scende in profondit

IL SISTEMA DI NAVIGAZIONE MULTILIVELLO

SDK 3.0 ha aggiunto un'utile propriet per impostare il tipo di visualizzazione predefinita da utilizzare per le celle, nel caso non si volesse realizzare una propria versione. Precedentemente era necessario posizionare i componenti visuali, utilizzando chiamate Objective-C, un'operazione che di ripeteva spesso in numerosi progetti, in questo modo si risparmia molto tempo, e si evita di incorrere in problemi di allineamenti e trasparenze errati. Gli stili di visualizzazione sono i quattro seguenti: UITableViewCell StyleDefault, UITableViewCell StyleSubtitle, UITa bleViewCellStyleValue1,UITable ViewCellStyleValue2. Il primo tipo consente l'inserimento di un'immagine sulla sinistra della cella, un testo e un opzionale simbolo di dettaglio, il secondo aggiunge una didascalia sotto alla voce principale, mentre gli ultimi due sono solo elenchi testuali, non consentendo l'inserimento di alcuna immagine. Il testo principale accessibile tramite la propriet textLabel, mentre quello pi piccolo utilizzando quello detailTextLabel.

IL PROCESSO DI CREAZIONE DELLE CELLE


Il procedimento necessario per la creazione di tutte le celle relativamente semplice: la tabella interroga il proprio delegato, UITableViewData Source, richiedendo il numero di celle, sinonimo di righe, rows, utilizzando il metodo numberOf RowsInSection, per poi popolare le celle iterativamente invocando il metodo cellForRow AtIndexPath. Se non viene specificato, come avviene in questo esempio il numero di gruppi disponibili, identificati dal termine sections, e
h t t p : / / w w w. i o p r o g r a m m o . i t

Fig. 8: I quattro tipi predefiniti di celle disponibili dalla versione 3.0 dell'SDK

i Ph on e p r o g r a m m ing

G 52 /Settembre 2009

27

iPhone: gestire i tabelle per rappresentare i dati MOBILE iPhone: gestire le tabelle per rappresentarele dati iPhone programming

Fig. 9: La tabella richiede il numero di righe e il numero di sezioni che la stessa deve ospitare

ottenuto dalla tabella invocando sul delegate il metodo opzionale sectionIndexTitlesForTableView, tale valore assunto come unitario e non verranno visualizzati header/footer e relative spaziature. Il metodo cellForRowAtIndexPath svolge quindi tutto il lavoro necessario per la generazione del componente visuale che verr utilizzato per la tabella:
- (UITableViewCell*) tableView:(UITableView*) tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

riceve per tale motivo come parametri la tabella, e le coordinate della cella, contenuti nella variabile indexPath, che nella sua versione pi semplice fornisce solo la riga richiesta, nel caso di un semplice array monodimensionale, ma quando si utilizzano strutture dati pi ramificate, questa caratterizzata da numerosi indici (ad esempio 1.4.5), come avviene per i paragrafi di un libro, allo scopo di identificare univocamente la posizione delle informazioni richieste. Tale metodo crea il componente visuale, un'istanza della classe UITableViewCell , figlia di UIView, configurandola attraverso l'inserimento di oggetti visuali, immagini e testi inclusi, richiedendo le informazioni al model (array o altra struttura dati): qui che generalmente viene scritta la maggior parte del codice, anche se, di recente, con l'aggiunta dei tipi di celle predefiniti, fornita dall'SDK 3, si notevolmente ridotta.

Fig. 10: La Tabella utilizza il file RootViewController come delegate per entrambi i protocolli

MIGLIORARE LE PERFORMANCE
Quando effettuate lo scrolling della tabella con un numero superiore a qualche decina, noterete inih t t p : / / w w w. i o p r o g r a m m o . i t

zialmente una fase di rallentamento iniziale, che successivamente non si presenter. Tale comportamento dovuto alla necessit da parte del sistema di effettuare caching delle celle mostrate. Nel caso andaste a visualizzare decine di celle in una sola schermata, potrete avere comunque fenomeni di rallentamenti, enfatizzati se avete personalizzato le vostre celle, aggiungendo immagini, testi, pulsanti, o altre UIView; ci dipende da come gestirete i contenuti presenti all'interno della singola cella; preferite quindi contenuti senza trasparenza, ottenuti impostando la propriet di opacit tramite Objective-C utilizzando nomevariabile.opaque = YES o tramite IB selezionando il checkbox Opaque (quando possibile). Tale accorgimento consente di migliorare, spesso in maniera evidente, il rendimento a video durante la fase di scrolling: ridurre al minimo gli oggetti visuali trasparenti quindi una buona operazione che non dovrebbe essere effettuata nella fase finale dello sviluppo; inserire, come stato detto, un certo numero di oggetti, pulsanti, campi di testo e/o immagini, in una cella ne degrada le performance: in tal caso una soluzione consiste nel rasterizzare, convertendo quindi in immagine, l'intero contenuto della cella che si vuole mostrare a schermo, e inserire solamente questo all'interno della stessa; in questo modo si migliorer notevolmente la prestazione complessiva del vostro software. Un ulteriore accorgimento pu essere quello di non creare una tabella per ogni livello di dettaglio, ma di modificare ogni volta sempre la stessa, in modo da ridurre al minimo il dispendio di risorse. Il nuovo iPhone 3GS, ha al suo interno un processore con circa 200 MegaHertz in pi rispetto alla precedente versione, ci significa un incremento effettivo di prestazioni comunque evidente, con relativo aumento di velocit di risposta nelle pi comuni operazioni, testare quindi il vostro gioco o applicativo solo su tale dispositivo potrebbe essere un grande errore, poich il numero di iPhone 3G/2G al momento maggiore rispetto a quello di esemplari del nuovo modello: cercate di testare anche su uno dei due precedenti, in caso non risultasse notevolmente lento anche su questi dispositivi ne trarrete sicuramente vantaggio, sia di feedback positivi ottenuti dagli acquirenti, che di download, e in caso di vendita, di guadagni, perch ovviamente raggiungerete un numero pi cospicuo di utenti interessati al vostro progetto.. Andrea Leganza

LAUTORE
Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali, e non, su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certificatd Expert, EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multidediali con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it

28

Phone pr r am in g Settembre i2009/o g53 m G

Gestire la memoria Gestire la memoria nelle applicazioni programming iPhone iPhone CORSO MOBILE nelle applicazioni iPhone

IPHONE: GESTIONE DELLA MEMORIA


N
CD WEB
ioProgrammoArt4.zip
cdrom.ioprogrammo.it

PER CHI SI ACCINGE A SVILUPPARE APPLICAZIONI PER LO SMARTPHONE DI CASA APPLE, UNO DEGLI ARGOMENTI SICURAMENTE PI OSTICI, LA GESTIONE DELLA MEMORIA. IN QUESTO ARTICOLO AFFRONTEREMO PROPRIO QUESTA IMPORTANTE TEMATICA
ell'articolo precedente abbiamo descritto la struttura, intesa come insieme di componenti visuali, che viene creata automaticamente quando realizziamo, attraverso l'utilizzo nel wizard, un progetto del tipo Navigation-Based Application; stato inoltre introdotto il componente chiamato UITableView e le relazioni che si instaurano tra la popolazione delle celle (sinonimo di righe della tabella) e il metodo cellForRowAtIndexPath, che, come spiegato, svolge tutto il lavoro necessario per la generazione di tali componenti visuali all'interno del delegate. Analizzeremo tale metodo in maniera dettagliata, in modo da introdurre alcune caratteristiche del linguaggio Objective-C che, potremo affermare senza alcun dubbio, essere le pi importanti, e anche pi impegnative da comprendere: per tale motivo vivamente consigliato testare personalmente i vari aspetti trattati in queste pagine per metabolizzarli correttamente.
#define NULL #ifndef nil #define nil NULL #endif __DARWIN_NULL

#endif /* ! NULL */

NIL, NIL, NULL E NSNULL


Ogni istanza di un oggetto, quando viene definita (il suo stato interno non quindi ancora stato inizializzato) assume automaticamente valore nil (ad esclusione di isa che assume la struttura della classe di cui la variabile istanza); in ambito Objective-C nil indica che una variabile (che dovrebbe puntare a un'istanza di un oggetto) non punta ad alcuna locazione di memoria. NULL, familiare a chi programma in C, anche disponibile nel linguaggio Objective-C, ovviamente questo dovuto al fatto che tale linguaggio una versione opportunamente modifica del C. nil viene quindi utilizzato per i puntatori ad oggetti, mentre NULL per qualunque puntatore (esistono ad esempio quelli a funzioni); entrambi, comunque, se andiamo ad analizzare la loro effettiva semantica, assumono valore pari a (void *)0.
#ifndef NULL

Se utilizzerete sempre Objective-C incontrerete molto raramente NULL, a meno di accedere ad alcune funzionalit particolari, che generalmente scendono a livelli pi bassi, in linguaggio C puro, quindi, comunque necessario essere a conoscenza della differenza tra i due e dei diversi contesti in cui li potete incontrare. Nil, con la prima lettera maiuscola, utilizzato per i puntatori a classi, e per tale motivo lo troverete ancora pi raramente, poich generalmente viene utilizzato lo stesso nil (sono anche questi sinonimi dal punto di vista sintattico). NSNULL presente invece in quelle classi che fanno parte della Foundation collection (NSArray, NSSet, NSDictionary e altre) poich queste non possono contenere il valore nil . Se vogliamo verificare che un oggetto appena stato creato, ma non ancora inizializzato, possiamo effettuare il seguente controllo:
if (myObject==nil) { //inizializzazione ... }

REQUISITI
Conoscenze richieste OOP Software MacOS X 10.5.4 o superiore, XCode Impegno

Tempo di realizzazione

Sarebbe ideale effettuare tale controllo prima di ogni utilizzo di una variabile, ma risulterebbe dispendioso sia in termini di memoria/risorse sia per numero di righe di codice, conviene quindi utilizzarlo in quelle situazioni in cui si dubbiosi sullo stato di una variabile, ad esempio quando questa viene creata in un metodo e utilizzata in un altro, e spiegheremo a breve perch tale tecnica pu risultare di fondamentale utilit. Inoltre, invocare qualunque metodo su tale variabile restituisce sempre 0 (in realt quasi sempre ,ma non ci addentriamo troppo) e viene quindi
h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 48 /Ottobre 2009

29

Gestire la memoria nelle applicazioni iPhone MOBILE Gestire la memoria nelle applicazioni iPhone iPhone programming

interpretato come false nei controlli condizionali, if, else, while etc. A differenza del linguaggio C, in cui invocare su una variabile NULL generalmente causa un crash dell'applicativo, in questo contesto si ottiene invece sempre un risultato da tali chiamate, anche se poi inutilizzabili nella pratica, oltre che sintomo di scarsa comprensione del funzionamento del linguaggio, e causa di dispendio di risorse, anche se modestissimo, ma che in un dispositivo con risorse relativamente limitate pu fare la differenza, dovuto allo scatenarsi di tutto il sistema di invocazione del metodo relativo (anche se inesistente); quindi perfettamente lecito effettuare la seguente chiamata:
NSString miaStringa; miaStringa = nil; [miaStringa isEqualTo:altraStringa];

IL METODO CELLFORROWATINDEX PATH


Dopo aver descritto cos' nil troverete sicuramente pi semplice la lettura del codice del metodo cellForRowAtIndexPath, che qui per chiarezza presentiamo nella versione prodotta direttamente dal wizard:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease]; } // Configurazione della cella return cell; }

Spesso, proprio utilizzare una variabile che assume il valore nil come parametro di un metodo, scatena una serie di problematiche che generalmente terminano con un crash del sistema: sono la causa pi frequente di problemi quando si programma con tale linguaggio e per evidenziare tali situazioni necessario generalmente utilizzare il debugger fornito, che, come scopriremo in un prossimo articolo, semplicemente il famoso gdb, proveniente direttamente dall'ambiente GNU/Linux (ma non solo). possibile assegnare in qualunque momento nil quale valore di una variabile, ma in tal caso c' il rischio di non rilasciare le opportune risorse associate a tale istanza (situazione identificata con il termine leak),
//effettuare prima la deallocazione delle risorse associate alla variabile myObject. myObject==nil //qualunque chiamata successiva non avr alcun risultato //Non effetturare tale operazione se la variabile non stata deallocata opportunamente!!! ... }

le motivazioni di tale affermazione verranno spiegate successivamente, quando si introdurranno i concetti relativi alla gestione della memoria. Per concludere questa sezione opportuno ricordare che esistono altri valori che assumono valore 0, ma utilizzati in contesti prettamente grafici: NSZeroPoint (un punto di coordinate 0,0). NSZeroSize (una dimensione NSSize di larghezza e lunghezza 0) e NSZeroRect (un rettangolo NSRect posizionato nell'origine di di larghezza e lunghezza 0);
h t t p : / / w w w. i o p r o g r a m m o . i t

Generalmente, una tabella ottiene i dati da una struttura dati, un array ad esempio, in ambiente Objective-C identificato dalla classe NSArray, e dovr mostrare, a meno di particolari condizioni, come un semplice filtraggio effettuato in una ricerca, tutti gli oggetti in essa contenuti e utilizzare i valori di ogni singola istanza per popolare le celle della tabella: se ogni oggetto conterr al suo interno una stringa, potremo utilizzare tale variabile per mostrare nella cella un testo visibile all'utente che scroller la tabella. Quando la tabella, come stato spiegato approfonditamente nell'articolo precedente, richiede al proprio delegate la struttura delle singole celle, operazione che avviene invocando il metodo suddetto, effettua sempre alcune operazioni, obbligatorie, e presenti nel metodo cellForRowAtIndexPath prodotto dal wizard: viene creato un certo numero di celle (graficamente parlando), che non corrisponde al reale numero di oggetti presenti nella struttura dati: quando verr effettuato lo scrolling della tabella tali celle verranno riutilizzate e ne verranno cambiati i valori interni, nel nostro caso, quindi, (ricordiamo che stiamo progettando unapplicazione agenda), l'azione da effettuare per una certa giornata: questo procedimento stato ideato per ottenere un notevole risparmio di risorse, si pensi che cosa succederebbe se si creassero tutte le istanze delle celle anche quando l'utente non le visualizza: quasi ogni applicativo verrebbe chiusa per saturazione della memoria disponibile nel dispositivo. Con questa tecnica viene risolto questo possibile collo di
i Phone ogr m in Ottobre 2009/ pr49amG g

30

Gestire la memoria nelle applicazioni iPhone Gestire la memoria nelle applicazioni programming iPhone iPhone MOBILE

bottiglia in maniera molto intelligente; quella descritta non comunque una tecnica presente solo in tale linguaggio: prende il nome di UI Virtualization, ed possibile ritrovarla, ad esempio, anche in Adobe Flex 4 per il nuovo componente DataGroup (Spark), e in Windows Presentation Foundation per il componente DataGrid fornito nel WPF Toolkit. La variabile statica cellIdentifier viene creata solamente durante la prima invocazione del metodo e permette di identificare la tipologia di cella che stiamo creando, utilizzando una stringa, che in questo caso corrisponde al valore Cell; nessuno vieta di creare diverse celle con diverse caratteristiche e decidere, a seconda delle necessit, quale utilizzare per un determinato oggetto prelevato dalla nostra struttura dati. Se volessimo quindi avere due celle diverse per qualche caratteristica, basterebbe effettuare la seguente operazione:
static NSString * CellIdentifierWhiteBackground = @"Cell"; static NSString *CellIdentifierBlackBackground = @"CellBlackBackground"; if (decisioneCellaNormale) UITableViewCell *cell =

LE REGOLE PER LA GESTIONE DEGLI OGGETTI


Queste regole sono i concetti fondamentali che devono essere sempre tenuti in considerazione durante lo sviluppo di qualunque applicativo, vengono introdotti solo ora perch negli articoli precedenti non erano utilizzati quegli aspetti del linguaggio che potevano indurre confusione e generare crash, inoltre si alleggerito il contenuto del primo tutorial : Un oggetto pu essere utilizzato da uno o pi proprietari (altri oggetti); quando un oggetto non ha proprietari, viene distrutto automaticamente senza alcun preavviso e in un momento imprecisato durante l'esecuzione del vostro applicativo dall'Autorelease Pool, generalmente dopo l'uscita dal metodo in cui stato creato; per impedire la sua distruzione automatica si deve notificare l'Autorelease Pool che si divenuti proprietari di tale oggetto (utilizzando il metodo retain); se non si pi interessati a tale oggetto, si deve smettere di esserne proprietari (utilizzando i metodi release/autorelease) per consentirne una possibile distruzione da parte dell'Autorelease Pool; il numero delle dichiarazioni di appartenenza (retain) deve essere bilanciato con quello dei rilasci (release) in modo da consentire il rilascio automatico; il numero di dichiarazioni di utilizzo viene chiamato retainCount e assume superiori o uguali a 0, in caso assuma quest'ultimo verr deallocato dall'Autorelease Pool; quando si raggiunge una certa esperienza opportuno utilizzare il pi possibile alloc/re tain/release invece di utilizzare autorelease, per motivi prestazionali, soprattutto all'interno di cicli con un numero consistente di iterazioni; il sistema invia un avviso di risorse insufficienti (principalmente RAM) quando questa decresce raggiungendo un valore prossimo ad 1.5MB. Se la prima regola un concetto condiviso da tutti i linguaggi Object-Oriented (e non solo), ed quella che consente di condividere una variabile tra pi istanze di una o pi classi, analizzando le altre si pu intuire che esistono due metodologie per gestire la memoria occupata da una variabile, una automatica (Autorelease Pool) e una manuale (alloc/retain/release) dove responsabilit del programmatore gestire il tutto; non sempre la modalit automatica risponde alle esigenze di sviluppo e per tale motivo entra in gioco la seconda.
h t t p : / / w w w. i o p r o g r a m m o . i t

NOTA

[tableView dequeueReusableCellWithIdentifier:CellI dentifierWhiteBackground]; { //codice di formattazione grafica e popolazione della cella } else //decisioneCellaBlackBackground UITableViewCell *cell = [tableView dequeueReusable CellWithIdentifier:CellIdentifierBlackBackground]; { //codice di formattazione grafica e popolazione della cella } return cell;

Per approfondire i concetti mostrati in questo articolo, consultare la guida chiamata Memory Management Programming Guide for Cocoa disponibile su sito developer.apple.com o all'interno della documentazione fornita con Xcode

ULTERIORI APPROFONDIMENTI

Potremo inoltre conoscere il tipo di cella richiamando la propriet (di cui parleremo successivamente in maniera approfondita, per ora identificatela come una variabile pubblica, accessibile quindi dall'esterno della classe) currentCell.reuseIdentifier che restituir la stringa utilizzata (nel nostro esempio corrispondente a CellWhiteBackground oppure CellBackBackground). Proseguendo notiamo che prima viene verificato che non esiste gi una cella con l'identificatore che abbiamo deciso (Cell) , in caso non sia stata gi creata viene invocato il costruttore della cella, utilizzando lo stile UITableViewCellStyleDefault (spiegato nell'articolo precedente) e tale istanza viene inserita nella Autorelease Pool.

i Ph on e p r o g r a m m ing

G 50 /Ottobre 2009

31

Gestire la memoria nelle applicazioni iPhone MOBILE Gestire la memoria nelle applicazioni iPhone iPhone programming

AUTORELEASE POOL
L'Autorelease Pool nasce come necessit, in ambiente Objective-C di fornire un sistema automatico di rilascio delle risorse non utilizzate, che molti potrebbero identificare con il sistema di Garbage Collection (GC) presente in numerose VM, Java e .NET in primis; poich nel dispositivo Apple non viene eseguito un vero motore di GC, principalmente per motivi prestazionali e di occupazione di risorse, quando avviamo i nostri applicativi (ricordo che alla fine sono scritti in linguaggio C) questa la soluzione automatizzata che ci viene fornita. Come funziona l'Autorelease Pool? Tale componente identificabile come un analizzatore di istanze di oggetti sulle quali stato invocato il metodo autorelease, sono stati quindi identificati come oggetti il cui ciclo di vita viene deciso dall'Autorelese Pool stesso; questo provvede a liberare la memoria, distruggere quelle istanze di oggetti non pi necessari, in maniera automatica, analizzando una particolare propriet chiamata retainCount (un variabile numerica presente in ogni istanza il cui funzionamento verr spiegato a breve): quando trover questa pari al valore 0. L'autorelease pool invoca sulla variabile un metodo, che viene identificato come il suo distruttore, chiamato dealloc, nel quale si effettuano generalmente operazioni di rimozione di legami con altre variabili (utilizzando il comando release/dealloc spiegati in seguito). Durante il ciclo di vita di una variabile, il retainCount viene quindi incrementato e decrementato, fino generalmente a terminare in un determinato istante t, in cui questo assumer valore 0: non detto che si raggiunga tale valore durante l'utilizzo del software, ammissibile che una variabile abbia retainCount superiore a 0 per tutta la sua esistenza, e termini la sua vita alla chiusura dell'applicativo. L'operazione di rilascio automatico avviene in maniera non prevedibile e non possibile quindi sapere quando viene effettuata in maniera

automatica, a meno di notificare tale evento utilizzando le stampe a schermo all'interno del metodo dealloc, generato automaticamente in ogni classe da parte del wizard; questa operazione realizzabile semplicemente aggiungendo una stampa a schermo prima dell'invocazione del metodo distruttore sulla classe padre:
-(void) dealloc { NSLog(@Istanza deallocata); [super dealloc]; }

In questo modo, osservando la finestra di log quando si avvia un applicativo in modalit di debug, si avr modo di sapere con una certa precisione quando un'istanza viene deallocata. Spesso, quindi, una variabile impostata per l'autorilascio (autorelease) che si utilizzava senza problemi durante l'esecuzione del proprio applicativo, in numerosi metodi nei quali si presupponeva, erroneamente, che non sia stata mai deallocata, a un successiva esecuzione dello stesso potrebbe causare in crash il sistema dopo alcuni secondi, o anche minuti; cosa accaduto? Nel primo caso il pool non ha effettuato pulizia durante l'utilizzo, mentre nel secondo caso ha provveduto a deallocarla automaticamente. Quando invochiamo autorelease su un oggetto richiediamo il decremento del suo retainCount di un'unit in un momento futuro, se questo scender a 0 ci sar il conseguente rilascio automatico. Se andiamo ad analizzare il contenuto del file main.m, generato automaticamente in ogni progetto dal wizard, troverete il seguente codice:
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; int retVal = UIApplicationMain(argc, argv, nil, nil); [pool release];

Fig. 1: L'Autorelease pool distrugge le istanze delle classi con retainCount pari a 0 automaticamente, invocando su di esse il metodo dealloc()

La prima riga crea e inizializza l'autorelease Pool, che identifichiamo come Main Autorelease Pool, cio autorelease pool principale, successivamente, nella seconda riga, viene avviato il nostro applicativo , e nella terza viene invocato il metodo release su tale pool. Tutto ci che contenuto tra la riga di creazione del pool e la chiamata del metodo release viene gestito da tale oggetto. possibile creare uno o pi release pool al di fuori del Main Autorelease Pool per rispondere a determinate esigenze? Certo, anche se i contesti in cui fatta questa scelta sono particolari, generalmente comunque ci si affida al Autorelease Pool principale, l'utilizzo pi frequente realizzato in maniera manuale da un programmatore, quello di realizzarne una versione ad
i Phone ogr m in Ottobre 2009/ pr51amG g

h t t p : / / w w w. i o p r o g r a m m o . i t

32

Gestire la memoria nelle applicazioni iPhone Gestire la memoria nelle applicazioni programming iPhone iPhone MOBILE

hoc all'interno di un ciclo nel quale vengono generate decine, se non centinaia o addirittura migliaia, di variabili temporanee ma viene sconsigliato in ambiente iPhone perch introduce un sovraccarico computazionale che in alcune situazioni potrebbe obbligare il sistema a terminare la vostra applicazione.

} //al tempo t (random) dopo l'uscita dal metodo verr deallocata automaticamente

AUTORELEASE POOL
Autorelease Pool provvede in maniera automatica e trasparente a rilasciare la memoria allocata per un oggetto in un non precisato momento durante l'esecuzione del vostro applicativo. Ogni istanza su cui non viene invocato il metodo alloc (ma anche , allocWithZone:, copy, copyWithZone:, mutableCopy, mutableCopyWithZone o retain, viene gestita da tale oggetto automaticamente.

NOTA

Creazione dell'account, per scaricare l'SDK gratuitamente e consultare la documentazione:


http://developer. apple.com/iphone/

RIFERIMENTI WEB

Un utilizzo generalmente pi comune quando si utilizzano i thread e si rende necessario creare una pool locale a tale ambiente di esecuzione. L'operazione di pulizia viene generalmente forzata quando le risorse di sistema scendono sotto alcuni limiti, se si verificano tali condizioni inoltre viene invocato per ogni UIViewController (e relative classi) il metodo didReceiveMemoryWarning, nel quale si dovrebbe provvedere alla rimozione manuale di quelle istanze non strettamente necessarie e non gestite dall'Atorelease Pool. obbligatorio invocare il metodo autorelease su una variabile per affidarla alla gestione dell'Autorelease Pool? No, tale operazione viene effettuata ogni volta che viene creata una variabile senza utilizzare il metodo alloc;
-(void) inizializzazione { NSString *newText = [NSString stringWithFormat @"Lavoro da fare."]; //autorelease automatico, retainCount scender da 1 a 0 in un non precisato istante dopo la fine del metodo; //area di utilizzo dellavariabile

Questo tipo di inizializzazione, fornito da NSString, utilizando uno dei suoi cosiddetto Factory Methods (metodi statici che generano su richiesta istanze della classe), permette di utilizzare la variabile all'interno del metodo in cui questa viene creata e verr rilasciata, in un non precisato momento, quando tale metodo terminer, ci avviene perch quando inizializziamo newText questa assume retainCount 1 e, alla prossima analisi da parte dell'Autorelease Pool, verr deallocata poich, essendo autorelease, subir un decremento automatico di tale valore di 1. Questo utilizzo generalmente non causa problemi, perch la deallocazione automatica da parte dell'autorelease Pool avviene sempre tra una chiamata e l'altra di un metodo; un approccio comunemente utilizzato quando si utilizzano cicli (while e for ad esempio) in cui si creano numerose variabili, ma si desidera che vengano rimosse automaticamente quando non pi necessarie (in alcune situazioni non la soluzione migliore in termini prestazionali, quindi necessario utilizzare la modalit manuale adoperando quindi alloc/retain/release. Se definissimo una variabile esternamente al metodo, per poi inizializzarla al suo interno per un utilizzo in uno o altri metodi in istanti successivi, verrebbe gestita tale situazione dall'Autorelease Pool?
NSString *newText; -(void)inizializzazione { newText = [NSString stringWithFormat @"Lavoro da fare."]; } -(void)mostraVariabile{ NSLog(@%@,newText); }

Fig. 2: Un esempio fornito dalla stessa Apple sul funzionamento dell'Autorelease Pool

Invochiamo il metodo inizializzazione per inizializzare la nostra variabile e poi, invochiamo in altri metodi quello chiamato mostraVariabile, noteremo che casualmente, otterremo dei crash del sistema: ci dovuto al fatto che l'autorelease Pool ha rilasciato la variabile tra due successive chiamate di mostravariabile (Fig.3): ecco uno dei motivi pi frequenti di crash della applicazioni su iPhone: assumere che una variabile sia sempre disponibile quando creata! Le regola, la ribadiamo, la seguente: se nella creazione della variabile non viene usato alloc oppure retain, come vedremo successivamente, tale variabile verr rimossa in maniera casuale e senza alcuna prevedibilit, memorizzate bene questo concetto perch altrimenti passerete intere giornate cercando di capire perch il vostro applicativo crasha casualmente.
h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 52 /Ottobre 2009

33

Gestire la memoria nelle applicazioni iPhone MOBILE Gestire la memoria nelle applicazioni iPhone iPhone programming

1.5MB, scatenando un sistema di avvisi e invocazioni di metodi automatici; per ogni istanza figlia diretta, o indiretta, di UIViewController verr invocato:
- (void)didReceiveMemoryWarning { NSLog(@"Memoria insufficiente ricevuto nel UIViewController!!!!"); }

Fig. 3: L'Autorelease distrugge newText tra un'invocazione e un'altra di mostraVariabile, generando un crash

mentre per il file chiamato nome_applicativo AppDelegate.m, quello responsabile della visualizzazione e della gestione del nostro applicativo :
- (void) applicationDidReceiveMemoryWarning: (UIApplication*)application { NSLog(@"Memoria insufficiente!!!!"); }

Come evitare questo problema di autorilascio? Un primo modo quello di utilizzare retain, in modo da notificare all'Autorelease Pool che vogliamo utilizzare tale variabile e che siamo in prima persona responsabili del suo rilascio, siamo quindi diventati quindi uno dei co-proprietari di tale variabile; il secondo consiste nel creare una variabile utilizzando alloc, metodo che spiegheremo nel prossimo articolo. Questo problema di accesso, dovuto a una scarsa comprensione della gestione in questo contesto, probabilmente uno dei motivi pi frequenti di crash quando si programma per i dispositivi di casa Apple.

NS*
Moltissime classi hanno un prefisso NS, questo dovuto al fatto che Mac OS X ha radici in NeXTSTEP, (di cui NS rappresentano le iniziali) linguaggio realizzato utilizzando Rhapsody e Mac OS, e che identifica storicamente molte delle classi che fanno parte delle API utilizzate per lo sviluppo su Mac.

SE SI ESAURISCONO LE RISORSE DEL DISPOSITIVO


Nonostante Apple abbia cercato di fornire al singolo applicativo che viene eseguito volontariamente dall'utente (sono ovviamente in esecuzione processi di supporto in maniera trasparente, come quelli di localizzazione, accelerometri e altri sensori, gestione della di telefonia etc) la massima quantit di memoria disponibile, sempre possibile incorrere nella situazione in cui si occupi una tale quantit di memoria durante l'esecuzione del proprio software, sia a causa dell'utilizzo di grandi strutture dati, sia per errori di programmazione, che generano i cosiddetti leak (locazioni di memoria occupate, ma non rilasciabili dall'Autorelease Pool e neppure dall'utente, di cui parleremo nel prossimo articolo), raggiungendo un valore prossimo a
h t t p : / / w w w. i o p r o g r a m m o . i t

Verr inoltre inviata al nostro applicativo una notifica (argomento non trattato in questa serie di articoli) del tipo UIApplicationDidReceiveMemoryWarningNotification. In ognuno di questi metodi dovremo provvedere in maniera manuale alla rimozione di risorse non necessarie al momento, oppure ricreabili quando necessario, come strutture dati ottenute prelevando le proprie informazioni da file disponibili localmente. Nelle due sezioni di codice utilizzate stato aggiunto un metodo NSLog, di stampa a schermo (simile a printf del C) allo scopo di consentire in fase di debug di avere un feedback immediato di cosa sta succedendo; comunque consigliato sempre testare il proprio applicativo sullo smartphone per verificare se il warning di memoria insufficiente viene invocato in qualche situazione. Per forzare questa situazione nel simulatore, che ricordiamo ha accesso alle nostre risorse hardware, parliamo quindi di almeno 1GB di RAM, contro i 128MB dell'iPhone 3G e i 256MB del 3GS, basta, durante una simulazione, cliccare sulla voce hardware presente nel menu del simulatore e selezionare la voce simulate memory warning. Non bisogna ignorare e neppure tralasciare una corretta implementazione di questi metodi, perch possono essere la soluzione per realizzare progetti di medie-grandi dimensioni dove le richieste di risorse possono superare quelle disponibili.

LAUTORE
Andrea Leganza Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali e non su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certified Expert, e EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multimediali, anche con iPhone, con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it.

CONCLUSIONI
In questo articolo, prettamente teorico, abbiamo acquisito ulteriori nozioni, che si riveleranno utili in qualsiasi contesto vi troverete in futuro; nel prossimo articolo continueremo a spiegare come viene gestita la memoria e parleremo di retain, retainCount, release e altri concetti altrettanto importanti . Buona programmazione. Andrea Leganza

34

i Phone ogr m in Ottobre 2009/ pr53amG g

I metodi di allocazione e rilascio degli oggetti inallocazione e rilascio degli oggetti in memoria I metodi di memoria iPhone programming MOBILE

LEAK E ZOMBIE IN AGGUATO...


P
CD WEB
ioProgrammoArt4.zip
cdrom.ioprogrammo.it

IN QUESTO ARTICOLO APPROFONDIREMO ULTERIORMENTE I CONCETTI LEGATI ALLA GESTIONE DELLA MEMORIA, IN MODO PARTICOLARE FAREMO LA CONOSCENZA DI QUELLI CHE IN GERGO VENGONO CHIAMATI ZOMBIE E LEAKS
rosegue in questo articolo la trattazione della gestione della memoria. Introdotto nel precedente numero della rivista, largomento consentir una comprensione pi approfondita dei vari elementi che abbiamo incontrato nel primo articolo, e che applicheremo, in tutti i futuri articoli dedicati al dispositivo mobile sviluppato dalla casa di Cupertino. oggetti che discendono da NSObject (che come abbiamo detto in un precedente articolo sono la maggior parte); retainCount assume un valore numerico intero, un contatore e indica il numero di oggetti che hanno chiamato il metodo retain su un determinato oggetto, hanno quindi dichiarato che la stanno utilizzando, e ne sono quindi co-proprietari: non venite tentati dal desiderio di utilizzare sistemi di stampa a schermo (NSLog) per visualizzarlo, perch otterrete spesso valori non coerenti, oppure superiori a quello previsto: per effettuare un'analisi su tale variabile entra in gioco Instruments, di cui parleremo in un altro articolo. RetainCount assume valore 1 quando la relativa variabile viene inizializzata. Spieghiamo meglio questo che uno dei concetti fondamentali del linguaggio ObjectiveC: normale quando si sviluppa un qualunque applicativo, passare variabili da una classe a una altra invocando i rispettivi metodi, durante questa operazione vengono utilizzati come parametri le istanze delle classi che abbiamo creato in precedenza, poich per, andando a scavare in profondit parliamo sempre degli amati/odiati puntatori del C, stato introdotto il concetto di retain per evitare che tali variabili vengano deallocate automaticamente se una o pi classi hanno dichiarato che la stanno utilizzando; in questo modo si riduce il rischio che avvengano crash in cascata dovuti alla deallocazione automatica effettuata dall'Autorelease Pool (mentre se deallocate manualmente tali variabili tali problemi si possono sempre presentare , e prendono il nome di Zombie) che, senza tale artificio, non avrebbe modo di valutare se una variabile inutilizzata oppure no. Ipotizziamo che una classe A invochi un metodo passaggioStringa su una classe B, passando un parametro di tipo NSString, una semplice stringa di testo (per la precisione un oggetto testo, differente sintatticamente dalle stringhe C che sono array di caratteri) quindi, se questa seconda classe desiderasse utilizzare in un altro momento, al di fuori del metodo in questione, tale variabile senza dover effettuare una
h t t p : / / w w w. i o p r o g r a m m o . i t

RETAIN E RETAINCOUNT
Come accennato nel numero precedente, esistono due modi per evitare che le proprie variabili vengano rimosse automaticamente dall'Autorelease Pool, utilizzare retain o alloc. Nel primo caso basta invocare tale metodo durante la creazione della variabile, dove viene invocato come detto in maniera trasparente il metodo autorelease, oppure successivamente, all'interno del metodo in cui questa viene creata.
NSMutableString *newText = [[NSMutableString initWithString: @"Lavoro da fare."] retain]; //inizializzata a retainCount = 1, invocato in maniera trasparente autorelease (-1 all'istante casuale t=x) e viene aggiunto 1 utilizzando retain: dopo il passaggio nell'Autorelease Pool avr quindi retainCount pari a 1=1(allocazione)-1(autorelease)+1(retain)

oppure:
NSMutableString *newText = [NSMutableString

REQUISITI
[newText retain]; Conoscenze richieste OOP Software MacOS X 10.5.4 o superiore, XCode Impegno

initWithString: @"Lavoro da fare."];

Tempo di realizzazione

In questo modo si creato un legame indissolubile tra la nostra istanza della classe e la variabile che stiamo utilizzando, abbiamo ora il pieno controllo sul suo tempo di vita, se non rilasceremo in futuro tale variabile non sar mai deallocata. Associata a retain esiste la variabile interna a ogni oggetto che abbiamo detto chiamarsi retainCount; tale variabile presente in tutti gli

i Ph on e p r o g r a m m ing

G 48 /Novembre 2009

35

I oggetti allocazione degli oggetti in memoria I metodiiPhone programming di allocazione e rilascio deglimetodi di in memoriae rilascioMOBILE

copia profonda (deep copy) del suo contenuto in una variabile locale, potrebbe facilmente effettuare la seguente operazione:
//Classe A; -(void)testRetainCount { //inivializzazione stringa ( autorelese) NSMutableString *stringaA = [NSMutableString initWithString[@Telefonare assistenza]]; //retainCount =1, essendo autorelase scender a retainCount=0 in un prossimo futuro, dopo l'uscita dal metodo corrente e verr deallocata; //chiamata del metodo di B passando la stringa [istanzaB passaggioStringa:stringaA]; //stringaA verr deallocata in futuro se non avr retainCount>0 } //Classe B NSMutableString *myString; -(void)passaggioStringa:(NSMutableString *)stringa{ myString = stringa; //myString punta alla stessa posizione in memoria di stringa [stringa retain]; //identico risultato se si effettua[myString retain] //stringa ora ha retainCount=2 non verr deallocata perch verr decrementata a 1 dall'autorelease pool. }

//chiamata del metodo di B passando la stringa [istanzaB passaggioStringa:stringaA]; } //Classe B NSString *myString; -(void)passaggioStringa:( NSMutableString *)stringa{ myString = stringa; } -(void)letturaMyString {NSLog (@%@,myString)}

All'uscita del metodo passaggioStringa la variabile stringaA verrebbe deallocata dopo l'uscita dal metodo testReatinCount e ogni accesso successivo in A, e in altri metodi di B, a myString (myString e stringaA sono sinonimi come spiegato) genererebbe un crash del sistema. La lezione da tenere bene a mente quindi quella di cercare di avere una chiara visione del tempo di vita delle variabili per evitare ore di debugging.

RELEASE DI UN OGGETTO
Quando decidiamo di notificare all'autorelease pool che non vogliamo pi utilizzare una variabile, non vogliamo quindi esserne proprietari (come stato detto pi opportuno dire coproprietari con altre istanze), invochiamo su di essa il metodo release, che decrementa di un'unit il suo retainCount: utilizziamo il comando [nomevariabile release], in questo modo, se la nostra variabile aveva retain count pari a 1 scender a 0 e verr rilasciata immediatamente: attenzione, quindi, se viene deallocata non potrete pi accedervi, e, nel caso lo faceste, causerete un crash del sistema (questa operazione errata di accesso viene identificata con l'accesso a uno Zombie, e ne parleremo verso la fine di questo articolo). Le regola per evitare di incorrere in decine di problemi di gestione della memoria quella di bilanciare il numero di retain con quello di release, in tal modo, quando si effettuer l'ultimo release, si far sempre scendere il retainCount a 0 e l'Autorelease pool invocher il metodo dealloc() sulla variabile (il metodo che svolge la funzione di distruttore, quindi responsabile della rimozione delle risorse utilizzat: variabili, strutture dati e canali di comuncazione in primis); ovviamente, se si desidera mantenere in vita un'istanza per tutto il tempo di vita della propria applicazione tale numero dovr essere sempre maggiore di 0. Un esempio pratico della variazione della variabile retainCount quello di pensare al numero di scalini necessari per raggiungere un piano, il numero di passi da effettuare per salire sulla sommit prima e poi riscendere sempre uguale.
i Phone ogr m in Novembre 2009/ pr49amG g

Poich stringaA stata creata immediatamente prima dell'invocazione del metodo, avr retainCount pari a 1, che diventer 0 in futuro poich autorelease, viene inoltre inserita nell'autorelease pool e per tale motivo verrebbe rilasciata al termine del metodo testRetainCount, ma ci non avviene poich all'interno di passaggioStringa tale valore viene incrementato a 2, utilizzando retain; la prima riga effettua una semplice copia tra puntatori, non viene quindi creata alcuna nuova variabile, ma myString punta alla stessa locazione di memoria di stringa, e non viene incrementato il retainCount di stringa, mentre la seconda riga incrementa di 1 il retainCount della variabile stringa, portandolo al valore 2; in questo modo si evita che questa venga deallocata automaticamente dallAutorelease Pool al termine del metodo testretainCount, infatti fuori da tale metodo avr valore 1 (dopo che autorelease avr svolto il suo compito). Effettuare il retain su stringa, oppure su myString ininfluente poich puntano allo stesso oggetto in memoria. Se non utilizzassimo retain all'interno di passaggioStringa:
//Classe A; -(void)testRetainCount { NSMutableString *stringaA = [NSMutableString initWithString[@Telefonare assistenza]];

h t t p : / / w w w. i o p r o g r a m m o . i t

36

I metodi di allocazione e rilascio degli oggetti inallocazione e rilascio degli oggetti in memoria I metodi di memoria iPhone programming MOBILE

Invece di release, che effettua il decremento immediatamente, si pu anche invocare manualmente autorelease, che provveder a decrementare retainCount in futuro: questo approccio non consigliabile ed meglio rila-

Fig. 2: Durante il tempo di vita di una variabile il retainCount sale e scende, fino generalmente a terminare in un determinato istante t

Fig. 1: Un tipico ciclo di vita di una variabile, quando il retaincount raggiunge il valore 0 viene automaticamente rilasciata; si noti inoltre il bilanciamento dei retain con quello dei release, che permettono di farlo scendere a 0

non verranno rilasciati se non quando l'array verr rilasciato; questa operazione automatica pu risultare per causa di problemi quali leaks e zombie (spiegati in questo stesso articolo). Se si vuole deallocare immediatamente una variabile, disinteressandosi del retainCount, baster invocare il metodo dealloc() su di essa, questa operazione e per sconsigliata, poich, ignorando il retainCount, si ignora quante altre istanze la stanno al momento utilizzando e si potrebbero avere numerosi crash del sistema (Zombie). Nella documentazione dell'API viene dichiarato se un certo metodo di una classe effettua oppure no il retain / release sull'oggetto passato come parametro, comunque possibile utilizzare Instruments per verificare tale comportamento.

NOTA

Per approfondire i concetti mostrati in questo articolo, consultare la guida chiamata Memory Management Programming Guide for Cocoa disponibile su sito developer.apple.com o all'interno della documentazione fornita con Xcode

ULTERIORI APPROFONDIMENTI

sciare subito una variabile, sia per liberare il prima possibile una risorsa (e questa una regola che si dovrebbe applicare in ogni linguaggio di programmazione, ma nel contesto mobile di fondamentale importanza dove le risorse sono pi limitate), ma anche per non incorrere nei problemi di accesso precedentemente spiegati, in primis a variabili deallocate automaticamente in un momento non precisato. possibile invocare numerose volte release? In sequenza certamente, ma non consigliabile se lo si effettua per risolvere problemi di gestione non corretta della memoria, ad esempio quando si dealloca un oggetto e non si effettua il release nel suo distruttore sulle variabili che questa utilizzava e si bypassa il problema in questo modo: generalmente rispecchia una cattiva comprensione del concetto di retain/release, pu comunque risultare necessario in particolari situazioni. Molti metodi effettuano un retain automatico, ad esempio quando aggiungiamo una variabile a un array, e dal punto di vista pratico risulta un'operazione automatica molto utile, poich gli oggetti inseriti vengono segnalati automaticamente di propriet di un determinato oggetto e

COSA SONO I LEAK


Non effettuare correttamente la gestione delle risorse in memoria porta ai cosiddetti leaks. Con tale termine si identifica quella variabile che non pi accessibile all'interno di alcun metodo di una classe e non vi quindi alcun modo per invocare su di essa il release/autorelease/dealloc: la variabile quindi irraggiungibile dal codice realizzato dall'utente e, essendo gestita dall'utente, non pu essere rimossa automaticamente dall'Autorelease Pool; in questo modo avremo un dispendio di memoria, in genere incrementale, che potrebbe portare, quando si parla di un numero di variabili medio grande (a seconda della loro singola occupazione di memoria), alla chiusura forzata del nostro programma. Un leak quasi sempre, quando non un bug di cui sono responsabili i tecnici di casa Apple (avviene ad esempio quando si utilizza il parser NSXML), segnale di una programmazione errata, disattenta o poco consapevole. Per monitorare tali situazioni si utilizza il tool chiamato Instruments, che fornisce in real-time dettagli sul numero e su
h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 50 /Novembre 2009

37

I oggetti allocazione degli oggetti in memoria I metodiiPhone programming di allocazione e rilascio deglimetodi di in memoriae rilascioMOBILE

quale sequenza di chiamate/comandi hanno generato tale leak. Un esempio di leak stato descritto nel paragrafo precedente, quando abbiamo parlato del mancato rilascio delle variabili inserite in un array; in tal caso, se non avremo pi accesso diretto all'array, non riusciremo pi a liberare gli oggetti inseriti al suo interno, che diverranno quindi leak.

da la regola del rapporto uno a uno tra retain e release. Attenzione, se la variabile viene dichiarata e definita in un metodo, quindi locale allo stesso, cos come nel caso che segue:
- (void) initMyString { NSMutableString *stringa; stringa= [[ NSMutableString alloc] initWithString:@"testo"]; [stringa release]; }

IL METODO ALLOC
Il secondo metodo per evitare che una variabile venga deallocata automaticamente, quello di crearla utilizzando il metodo alloc, che imposta il suo retainCount a 1 (effettua quindi un retain automatico), e sar quindi nostra cura decrementarlo utilizzando release/autorelease per consentire, poi, nel caso tale valore raggiunga valore 0, all'Autorelease Pool di invocare su di esso il metodo dealloc. Quando si usa alloc, si richiede che venga allocato lo spazio in memoria necessario per contenere tale istanza. Per inizializzare il suo contenuto si invoca generalmente un metodo che inizia con il prefisso init, che completa, quindi, la fase di impostazione del suo stato interno. La mancata invocazione di un metodo init su un'istanza, la rende inutilizzabile nella pratica; poich nel linguaggio Objective-C non esiste una regola per identificare un costruttore, e non neppure obbligatorio crearlo, anche se risulterebbe di scarsa utilit non realizzarne almeno uno, si ha la possibilit di creare un numero indefinito di costruttori che soddisfino le proprie necessit. Generalmente, comunque, viene utilizzato il prefisso init per ognuno di questi, quindi baster digitare init quando comparir la lista di autocomplete per verificare quali sono stati resi disponibili. Il costruttore pi semplice caratterizzato dalla seguente signature (id) init(): di questo ne parleremo approfonditamente in un prossimo articolo.
NSMutableString *stringa; stringa = [[NSMutableString alloc] initWithString: @testo]; //retainCount=1

se non si provvede a rilasciarla al suo interno, prima della fine del metodo, avremo un leak, poich fuori da questo non sar pi possibile accedervi e rilasciarne la memoria. Un utilizzo che non presenta tale inconveniente quello in cui la variabile appartiene a una istanza della classe, ed accessibile in qualunque suo metodo interno:
NSMutableString *stringa; - (void)initMyString { stringa = [[ NSMutableString alloc] initWithString:@"testo"]; } -(void) removeString {[stringa release]; }

Poich abbiamo accennato dell'inserimento di oggetti in un array, viste le conoscenze ora acquisite, si pu affrontare il problema in due modi: utilizzando come struttura dati un NSMutable Array, ovveroun array la cui dimensione pu cambiare dinamicamente, semplicemente aggiungendo/rimuovendo oggetti e, quindi, invocando alcuni metodi. Leggiamo prima un estratto della documentazione associata a tale classe: If you do not use garbage collection, when you add an object to an array, the object receives a retain message. When an object is removed from a mutable array, it receives a release message. If there are no further references to the object, this means that the object is deallocated. Poich, come stato gi detto in precedenza, non siamo in in ambiente con un vero Garbage Collector, ogni oggetto che verr inserito nell'array ricever un retain, mentre ricever un release quando verr rimosso, oppure quando si invocher release sull'array stesso. Simuliamo l'inserimento:
NSMutableArray *array = [[NSMutableArray alloc] init]; for (int i = 0; i < 10; i++) { NSMutableString *convenienceString= [NSMutableString stringWithString:@"testo"];

In questo caso abbiamo creato un'istanza di un oggetto appartenente alla classe NSMutableString, in grado di contenere una stringa di dimensione variabile, prima definendola, poi allocandola e infine inizializzando il suo valore interno con una stringa; il retainCount pari a 1 e potremo utilizzarla in qualunque metodo noi desideriamo, poich il contatore non verr mai decrementato automaticamente. Quando sar necessario, potremo invocare su di essa il metodo release e farla deallocare, quindi sempre valih t t p : / / w w w. i o p r o g r a m m o . i t

38

i Phone ogr m in Novembre 2009 /pr51amG g

I metodi di allocazione e rilascio degli oggetti inallocazione e rilascio degli oggetti in memoria I metodi di memoria iPhone programming MOBILE

//retainCount 1 e autorelease [array addObject:convenienceString]; //retain automatico del metodo sul parametro retainCount 2 di convenienceString } ...autorelease automatico al tempo x: su tutte le variabili inserite nell'array assumono retainCount= 1... [array release] //array invia release alle proprie variabili quindi il loro retainCount scende a 0 e vengono deallocate, array viene ovviamente rilasciato dopo questa operazione e deallocato se il suo retainCount=0 (non effettua un controllo se il retainCount degli oggetti al suo interno >0)

NOTA

I possessori di Snow Leopard hanno modo di utilizzare il software di analisi statica chiamato Clang, integrato con XCode 3.2. Purtroppo non installabile su Leopard 10.5. Questo tool fornisce informazioni dettagliate su tutti i punti in cui possono verificarsi Zombie e Leaks: baster invocare il comando Build and Analyze presente nel menu Build.

SNOW LEOPARD E XCODE 3.2

Non abbiamo utilizzato alloc, quindi tutte le istanze di convenienceString vengono rilasciate automaticamente quando rilasceremo l'array (perch sono autoreleased). convenienceString assumer retainCount pari a 2 poich addObject, da come dichiarato esplicitamente dalla documentazione dell'API, effettua un retain sul parametro passato al metodo addObject; essendo di tipo autorelease ognuna delle dieci istanze inserite nel ciclo for verr decrementata a 1 automaticamente, il successivo rilascio dell'array effettuer un'ulteriore release su ognuno di questi, e il loro retainCount verr portato a 0, sar perci invocato il dealloc su ogni variabile di tipo NSMutableString che abbiamo inserito.
NSMutableArray *array = [[NSMutableArray alloc] init]; for (int i = 0; i < 10; i++) { NSMutableString *convenienceString= [NSMutableString stringWithString:@"testo"]; [array addObject:convenienceString]; //retaincount di convenienceString = 2 [convenienceString release]; // retaincount di convenienceString = 1 [array release]; //array invia release agli oggetti che contiene, quindi convenienceString assumer retainCount=0 e verr dellocato; non si avranno quindi leaks }

re immediatamente le risorse, e risulta quindi molto adatto in situazioni in cui si genera un numero consistente di inserimenti. Confrontiamo le due soluzioni: nel primo caso avremo un numero di oggetti incrementale, si passer da 1+1 della prima iterazione a n+n quando si arriver all'ultima, tale numero diventer poi pari a n solo dopo il termine del metodo, quando le istanze allocate riceveranno l'autorelease; nel secondo caso a ogni iterazione avremo in memoria un numero di oggetti pari a quelli inseriti nell'array e a quello che verr rilasciato alla fine dell'iterazione, quindi da 1+1 a n+1 (n istanze inserite nell'array e l'oggetto allocato di tipo NSNumber). Esiste una terza alternativa, che consiste nell'utilizzare un Pool locale a tale metodo che conterrebbe le variabili create nel primo esempio e le rilascerebbe al termine del ciclo, ma non complichiamo ulteriormente. Bisogna comunque tenere quindi a mente certe considerazioni quando si lavora su strutture dati popolate con oggetti di dimensioni non ridotte.

ZOMBIE E EXC_BAD_ACCESS
EXC_BAD_ACCESS rappresenta uno di quegli errori che riceverete pi frequentemente quando inizierete a creare istanze di classi in Objective-C, questo errore viene generato quando il vostro codice tenter di accedere ai cosiddetti Zombie, sinonimo di istanze di classi non pi disponibili, perch deallocate in precedenza. Prendiamo in considerazione come esempio il seguente codice:
NSMutableString *stringa; -(void) scatenaZombie { [self initMyString]; [self printMyString]; } - (void) initMyString { stringa = [[ NSMutableString alloc] initWithString:@"testo"]; [stringa release]; //la variabile viene deallocata } -(void) printMyString { NSLog (%@,stringa); //accesso ad uno zombie }

In questo caso abbiamo utilizzato alloc, quindi necessario rilasciare la variabile per evitare leaks, poich altrimenti tutte le istanze di allocedNumber presenti nell'array avranno retainCount pari a 2 e, quando proveremo a rilasciare l'array, il loro retainCount scender a 1 impedendone il rilascio e generando un leak dieci in questo preciso esempio, (poich deallochiamo l'array non potremo pi accedervi e, conseguentemente, non avremo modo di accedere a queste variabili con retainCount pari a 1). Questo secondo approccio ha il pregio di libera-

Inizializzando nel metodo initMyString la nostra stringa, e successivamente deallocandola richiamando il metodo release, abbiamo posto fine al suo tempo di vita; se per invocheremo successivamente a tale metodo quello chiamato printMyString riceveremo un errore da parte del debugger in esecuzione, poich tale metodo prova ad accedere a un'istanza non pi disponih t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 52 /Novembre 2009

39

I oggetti allocazione degli oggetti in memoria I metodiiPhone programming di allocazione e rilascio deglimetodi di in memoriae rilascioMOBILE

bile. Questo errore si presenta anche nel caso in cui si sbilanci il numero di retain e release, invocando quindi un numero di questi ultimi maggiore del retainCount disponibile:
-(void) scatenaZombie { [self initMyString]; [self removeMyString]; } - (void) initMyString { stringa = [[ NSMutableString alloc] initWithString:@"testo"]; [stringa release]; //la variabile viene deallocata } -(void) removeMyString { [stringa release]; //secondo release : accesso ad uno zombie }

NSLog(@"NSZombieEnabled!!! Disable when compiling for release");}...

Ricordatevi sempre di disabilitarla quando compilerete il vostro software per l'Apple Store. Per avere ulteriori informazioni su tale errore bisogna impostare un breakpoint che permetter di avviare il debugger con un dettagliato, e pi utile, stack delle chiamate. Selezionando la voce del menu Run->Show->BreakPoints si presenter una finestra dove possibile inserire e visualizzare i breakpoint impostati. Aggiungiamone uno inserendo il seguente testo:
-[_NSZombie methodSignatureForSelector:]

NOTA

ZOMBIE E LEAKS

Purtroppo questo tipo di errore non viene corredato da informazioni sufficienti per comprendere in quale porzione di codice avviene il problema, e per risolvere tale mancanza del debugger necessario impostare un particolare parametro di configurazione all'interno delle opzioni del file eseguibile generato da XCode. Fate doppio clic sulla voce (ha il nome del vostro progetto) presente come riga all'interno nella colonna di sinistra di XCode, Goups & Files, sotto la categoria Executables e selezionate Arguments dal menu in alto; inserite una riga all'interno del box Variables to be set in the Enviroment con il seguente valore: NSZombieEnabled e impostando come valore YES. Dopo aver effettuato questa operazione, invece di un laconico EXC_BAD_ACCESS, otterremo una stringa leggermente pi interessante.
*** -[CFString retain]: message sent to deallocated instance 0xd21b50

Impostando un breakpoint su tale metodo, si far in modo che il debugger interrompa l'esecuzione dell'applicativo prima che questo crashi, fornendo informazioni sullo stack trace e mostrando quale variabile ha generato il crash. Quando si verificher un accesso a uno Zombie potrete quindi ottenere informazioni dettagliate, ma soprattutto il nome della variabile e identificare il metodo in cui avvenuto l'errore. Questo sistema non funziona purtroppo con tutte le classi fornite dallSDK, e in tal caso diventa tutto pi complicato. Per ridurre al minimo questo problema di accesso, basta impostare a nil il valore della variabile, immediatamente dopo il suo rilascio (dopo il release o autorelease), in tal modo qualunque invocazione non avr alcun risultato, ma almeno non far crashare il software.

Si accede a uno zombie quando si invocano metodi o si richiedono variabili di un'istanza non pi disponibile, perch deallocata (autorelease automatico oppure release manuale); un leak, invece, identifica un'istanza non pi accessibile da parte del codice, poich non stata deallocata quando era invece necessario.

LAUTORE
Andrea Leganza Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali e non su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certified Expert, e EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multimediali, anche con iPhone, con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it.

CONCLUSIONI
Tutto quello che abbiamo spiegato in questi due articoli si applica a quasi tutte le classi discendenti da NSObject (NSString ad esempio si comporta in modo diverso, e spiegheremo il perch nel prossimo articolo), purtroppo perde di validit quando utilizziamo strutture dati e tipi di dati presi direttamente dal linguaggio C, ad esempio creando variabili di tipo int o float, che in mano a un programmatore accorto possono fare la differenza in termini di prestazioni per un software. In questo articolo, prettamente teorico, abbiamo acquisito ulteriori nozioni, che si riveleranno utili in qualsiasi contesto vi troverete in futuro; nel prossimo numero della rivista torneremo a trattare della popolazione della tabella e modificheremo il codice creato in automatico per realizzarla; mostreremo anche come popolare le relative celle. Buona programmazione. Andrea Leganza

Questa riga di errore ci fornisce l'indirizzo in memoria dell'oggetto che ha causato il crash del software, ma non ancora sufficientemente comodo per identificare velocemente quale istanza genera il problema. Questa impostazione deve essere utilizzata solamente in fase di sviluppo e non in fase di rilascio poich non rilascia alcuna variabile allo scopo di consentire lo studio delle variabili Zombie! Una soluzione per evitare di dimenticarsi tale impostazione attiva, consiste nell'inserire il seguente codice nel file NomeApplicazioneDelegate.m come prima riga del metodo applicationDidFinishLaunching:
- (void)applicationDidFinishLaunching:(UIApplication *)application { if(getenv("NSZombieEnabled")) {

h t t p : / / w w w. i o p r o g r a m m o . i t

40

i Phone ogr m in Novembre 2009/ pr53amG g

La gestione delle tabelle negli applicativi Apple iPhone negli applicativi Apple iPhone La gestione delle tabelle iPhone programming MOBILE

COME POPOLARE UNA UITABLEVIEW


N
CD WEB
ioProgrammoArt4.zip
cdrom.ioprogrammo.it

IN QUESTO ARTICOLO POPOLIAMO LA NOSTRA TABELLA CON UN ELENCO DI VOCI OTTENUTE INTERROGANDO UNA SERIE DI STRUTTURE DATI. NELLA FATTISPECIE VEDREMO COME INSERIRE DELLE SEMPLICI RIGHE, CREARE DELLE SEZIONI E NAVIGARE TRA LE STESSE

ei numeri precedenti di questa seconda serie di articoli dedicati alla programmazione delliPhone abbiamo momentaneamente abbandonato il nostro progetto, una ToDo List, per trattare alcuni argomenti di fondamentale importanza relativi al linguaggio Objective-C e alla gestione della memoria in un ambiente non governato da un moderno strumento automatico qual il Garbage Collector. Riprendiamo ora a trattare della popolazione della UITableView che avevamo creato utilizzando il wizard di XCode e selezionando il progetto di tipo Navigation-based Application.

METODI PER POPOLARE UNA TABELLA


Per semplicit riproponiamo il corpo del metodo tabelView;cellForRowAtIndexPath che viene generato automaticamente dal wizard:
// Customize the appearance of table view cells. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier] autorelease];

REQUISITI
Conoscenze richieste OOP Software MacOS X 10.5.4 o superiore, XCode Impegno

} // Configure the cell. return cell; }

Tempo di realizzazione

Poich abbiamo sufficientemente trattato in precedenza di alloc, autorelease e nil, iniziamo immediatamente a popolare la nostra tabella nel modo pi semplice: inserendo manualmente una stringa di testo allinterno di tutte le righe della nostra lista di cose da fare. Prima di ci, doveroso descrivere

molto brevemente la struttura tipica di una cella, almeno riguardo le versioni fornite nellSDK. Una cella svolge la funzione di unit atomica di ogni tabella, ma in pratica un contenitore anchessa di numerosi componenti: trovano posto, almeno nelle versioni predefinite, due istanze di UILabel, una disponibile con lidentificatore di textLabel, laltra come detailTextLabel, una UIView con il nome accessoryType, un UIImageView con il nome di imageView, e terminiamo con una UIView chiamata backgroundView; in realt la lista di componenti disponibili non terminata, ma generalmente questi sono quelli utilizzati nella maggior parte di applicazioni, a meno di realizzare customizzazioni relativamente complesse; le due UILabel svolgono il compito di mostrare il testo associato alla riga, laccessoryType consente di impostare il tipo di icona da posizionare sulla destra della cella per indicare la possibile presenza di un ulteriore livello di dettaglio, la imageView, quando viene associata a unimmagine, la mostrer sul lato sinistro della cella, prima del testo, la backgroundView consente di variare in maniera relativamente avanzata laspetto della cella. Nel caso in cui variare le impostazioni di questi componenti non ottenesse i risultati sperati, si potranno aggiungere uno o pi oggetti accedendo alla propriet chiamata contentView, anchessa un UIView, che svolger per questi nuovi oggetti la funzione di superview: in caso di necessit sar quindi questo componente che dovrete utilizzare per inserire altre UILabel, immagini o altro; se invece si richiedesse una completa ristrutturazione della singola cella, sar necessario ignorare linizializzazione utilizzando gli stili predefiniti (quindi il metodo initWithStyle:reuseIdentifier utilizzato dal wizard) e invocare la versione minimale dellinizializzazione (init) e inserire e configurare manualmente ogni componente (impostando posizioni, dimensioni e parametri). Per effettuare una customizzazione anche possibile utilizzare Interface Builder, ma non tratteremo di questo aspetto nel presente articolo. Per rendersi conto delle potenzialit delle customizh t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 44 /Dicembre 2009

41

La gestione delle tabelle MOBILE La gestione delle tabelle negli applicativi Apple iPhone negli applicativi Apple iPhone iPhone programming

zazioni possibili per le celle basta semplicemente aprire i settings/impostazioni disponibili su iPhone. Quasi ogni schermata presenta una diversa versione di cella. Generalmente si impostano le caratteristiche estetiche comuni a tutte le celle, e che per tale motivo resteranno immutate per tutta la vita della tabella, allinterno del blocco di testo in cui queste celle vengono allocate e inizializzate (quindi allinterno del blocco relativo a if(cell==nil)); immediatamente dopo di questo si inserir il codice necessario a customizzare la riga, in base al contenuto relativo alla singola variabile associata. Modifichiamo il tipo di cella passando dal tipo predefinito UITableViewCellStyleDefault a quello UI TableViewCellStyleValue1 per consentirci di inserire un testo nella parte destra della cella.
// Customize the appearance of table view cells. ... if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease]; ... } // Configure the cell. //Oltre questo commento inseriremo il codice per modificare i testi delle celle

(UITableView *)tableView { return 1;} // Customize the number of rows in the table view. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 0; }

Come si pu notare, per default il wizard restituisce per ogni sezione presente, una in questo caso, il valore zero (0): ci impedisce la creazione di una o pi celle: modifichiamo con un valore maggiore di zero, ad esempio 15:
// Customize the number of rows in the table view. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return 15; }

Salviamo e avviamo il simulatore: finalmente otte-

NOTA

RIFERIMENTI WEB
Creazione dell'account per scaricare l'SDK e consultare la documentazione:
http://developer. apple.com/iphone

Ora possiamo impostare il testo che verr visualizzato per tutte le righe; per fare ci dovremo accedere a uno dei componenti visuali contenuti nella cella, identificato dal nome textLabel, tale componente, come stato detto, non altro che una UILabel che consente di mostrare a schermo una stringa di testo semplicemente impostando il valore della sua propriet text:
... cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease]; // Configure the cell. cell.textLabel.text= @"Riga di testo"; return cell }

Fig. 1: La tabella mostra un numero di righe a seconda del valore ottenuto invocando tableView: numberOfRowsInSection:

Avviamo il simulatore, notiamo per che non verr visualizzato alcun testo: questo dovuto al fatto che, come abbiamo spiegato in precedenza, ogni tabella richiede al proprio delegate (in questo caso la nostra classe RootViewController generata dal wizard), attraverso linvocazione su di questo di due metodi numberOfSectionsInTableView: e tableView: numberOfRowsInSection, il numero di sezioni e quello di righe per ogni sezione disponibile:
- (NSInteger)numberOfSectionsInTableView:

niamo un elenco, di scarsa utilit pratica, ma di indubbio valore didattico. Proviamo ora ad aggiungere un testo nella parte destra. Questa operazione la si compie, cos come abbiamo fatto per il cell.textLabel, accedendo a cell.detailTextLabel, che sempre unistanza della classe UILabel:
... cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease]; cell.textLabel.text= @"Riga di testo"; cell.detailTextLabel.text = @"Testo";

Se avessimo utilizzato come style in fase di inizializzazione della cella quello di default (initWithStyle:UITable
i Phone ogr m in Dicembre 2009/ pr45amG g

h t t p : / / w w w. i o p r o g r a m m o . i t

42

La gestione delle tabelle negli applicativi Apple iPhone negli applicativi Apple iPhone La gestione delle tabelle iPhone programming MOBILE

ViewCellStyleDefault), avremo ottenuto come risultato la visualizzazione di tale stringa di testo immediatamente sotto textLabel, con questo stile, invece, tale UILabel presentata nella modalit che riteniamo pi comoda per una rapida consultazione di una todo list. Impostiamo infine il simbolo grafico di dettaglio, disclosure button (Fig. 2), identificato con il simbolo di maggiore, per fare ci dovremo impostare tale propriet delle celle allinterno del metodo in cui creiamo le istanze delle celle, parliamo quindi sempre di tabelView;cellForRowAtIndexPath:
/if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleValue1 reuseIdentifier:CellIdentifier] autorelease]; cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; }

Completiamo questa prima versione impostando il titolo che viene mostrato nella Navigation Bar, la barra di colore blue posizionata nella parte pi alta della nostra applicazione, per fare ci decommentiamo il metodo viewDidLoad e aggiungiamo la seguente riga:
- (void)viewDidLoad { [super viewDidLoad]; self.title = @"ToDo List"; // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem; }

Perch abbiamo inserito questo semplice comando allinterno del metodo viewDidLoad? Questo uno dei primi metodi che vengono invocati esplicitamente (ancora prima viene invocato il metodo initWithNibName) quando viene analizzato il file xib e lengine del telefono si prepara per mostrarlo visivamente: dopo viewDidLoad ogni istanza, direttamente o indirettamente legata a UIViewController, invocher in sequenza viewWillAppear e viewDid Appear (anche questi inseriti automaticamente dal wizard e anche questi da decommentare in caso di necessit); la scelta caduta su viewDidLoad perch uno dei metodi consigliati dalla stessa documentazione per effettuare al suo interno operazioni necessarie per la configurazione sia visuale che strutturale del viewcontroller. Cambiamo ora il tipo di tabella da plain, quindi visualizzata come un continuo elenco in cui il nome delle sezioni viene presentato come un testo su uno sfondo blu scuro, a grouped, allo scopo di distanziare meglio i diversi giorni della settimana (senza doverci preoccupare di impostare parametri per gli header e i footer), per fare ci apriamo il file rootviewcontroller.xib con Interface Builder e, dopo aver selezionato la tabella, utilizzando il Property Inspector, selezioniamo grouped dalla

Fig. 3: Impostiamo a grouped il tipo di visualizzazione della tabella

NOTA

Gli stili predefiniti per le celle, introdotti nellSDK 3.0, sono : UITableViewCellStyleDefault, UITableViewCellStyleValue1, UITableViewCellStyleValue2, UITableViewCellStyleSubtitle;

GLI STILI PREDEFINITI

voce a tendina. A questo punto testiamo il comportamento della tabella aumentandone il numero di sezioni, impostiamo a 7, i giorni della settimana che mostreremo nella schermata:
- (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { return 7;}

Mostriamo ora il titolo per ognuna delle sette sezioni, il giorno della settimana nel nostro caso; per fare ci utilizzeremo il metodo richiesto dalla tabella alla nostra classe/delegate, la cui signature tableView :titleForHeaderInSection:; tale metodo riceve come parametro un valore numerico a indicare la sezione che la tabella sta analizzando, un numero nellintervallo compreso tra da 0 e numerosezioni-1 (7-1 = 6 in questo caso);
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section

Fig. 2: Abbiamo ora ottenuto una struttura base per una tabella

{ switch (section) {

i Ph on e p r o g r a m m ing

G 46 /Dicembre 2009

h t t p : / / w w w. i o p r o g r a m m o . i t

43

La gestione delle tabelle MOBILE La gestione delle tabelle negli applicativi Apple iPhone negli applicativi Apple iPhone iPhone programming

case 0: return @"Sunday"; break; case 1: return @"Monday"; break; case 2: return @"Tuesday"; break; case 3: return @"Wednesday"; break; case 4: return @"Thursday"; break; case 5: return @"Friday"; break; case 6: return @"Saturday"; break; default: return @""; break; } return @""; }

Per ora abbiamo sufficientemente strutturato la nostra interfaccia grafica, dobbiamo ora realizzare un sistema per gestire le nostre informazioni in modo strutturato e facilmente modificabile.

NSMutableArray le impedisce per di essere la soluzione pi performante rispetto ad NSArray, sia per i meccanismi interni necessari per farla operare, che in termini di occupazione di memoria a causa del codice aggiuntivo inserito al suo interno sotto forma di metodi e altre strutture aggiuntive. Per fortuna convertire una struttura di un tipo allaltro nel caso si sia rivelata la scelta meno adatta unoperazione relativamente indolore (pi leggera da NSArray a NSMutableArray, viceversa pi complessa). Nel nostro caso, per ridurre il numero di righe di codice da digitare, utilizzeremo NSMutableArray, per gestire i vari task delle singole giornate, mentre un NSArray per raggruppare le informazioni relative ai sette giorni della settimana.

NSARRAY E NSMUTABLEARRAY
Quando si mostra il contenuto di una tabella necessario accedere in qualche modo al componente Model del pattern MVC, mentre la tabella incarna il View (e il Controller): a seconda delle necessit,si deve scegliere tra una struttura dati di tipo statico, immutabile dopo la sua creazione o dinamico (nella documentazione queste classi contengono generalmente il termine mutable). Tra le strutture disponibili vengono spesso adoperate NSArray e NSMutableArray, facenti parte della libreria Foundation.framework; la prima una classe il cui scopo quello di contenere un elenco di istanze di altre classi, impedendone per qualunque modifica successivamente alla sua creazione in termine di numero e istanza: non quindi possibile cancellare oppure inserire nuovi elementi; NSMutableArray semplicemente una versione opportunamente modificata di NSArray ( infatti una sua classe direttamente derivata) che consente inserimenti, in praticamente ogni posizione, testa, coda e nel mezzo, e cancellazioni in maniera completamente libera. La scelta di quale struttura sia la pi adatta in un determinato contesto unoperazione generalmente immediata: se il numero di celle pu cambiare nel tempo, prevalentemente ad opera dellinterazione dellutente, la scelta in genere cade su NSMutableArray per evitare inutili operazioni di cancellazione e ripopolamento della struttura dati: inserire un nuovo oggetto in un NSMutableArray una singola operazione, mentre nel caso di un NSArray comporta una copia profonda della struttura precedente, con un numero di iterazioni quasi sempre pari al numero di elementi presenti (effettuati con un ciclo for oppure utilizzando la fast enumeration ad esempio). La rimozione di un elemento da un NSMutableArray anche in questo caso ottenuta con una singola operazione, mentre nel caso dellNSArray ci comporta sempre effettuare una copia profonda nella quale viene ignorato lelemento da cancellare. La grande flessibilit di
h t t p : / / w w w. i o p r o g r a m m o . i t

NSDICTIONARY E NSMUTABLEDICTIONARY
Ora che abbiamo definito quale struttura dati conterr i nostri elenchi di cose da fare, si pone il problema di come rappresentare il singolo task della nostra todo list: analizzando la sua struttura identifichiamo alcune informazioni che identificherebbero il suo stato: una descrizione, che mostreremo nella textLabel e lo stato (Da fare, Fatto, Dimenticato) che verr utilizzato per le UILabel di tipo descriptionTextLabel. possibile utilizzare diverse soluzioni in questo contesto: potremo creare una classe di tipo NSObject e al suo interno inserire due campi di tipo NSString, oppure delle struct, ma per fare qualcosa di diverso dal solito approccio Object Oriented, adopereremo una classe presente in

NOTA

ULTERIORI APPROFONDIMENTI

Per approfondire questi concetti consultare la guida chiamata Table View Programming Guide for iPhone OS disponibile su sito developer.apple.com o all'interno della documentazione fornita con XCode.

Fig. 4: Una serie di sezioni con tre righe per ognuna

44

i Phone ogr m in Dicembre 2009 /pr47amG g

La gestione delle tabelle negli applicativi Apple iPhone negli applicativi Apple iPhone La gestione delle tabelle iPhone programming MOBILE

NOTA

Allinterno di Foundation.framework troviamo decine di classi, tra cui NSDictionary e NSMutableDictionary, NSarray e NSMutableArray, NSString e NSMutableString, NSData e NSMutableData, NSDate e NSError: probabilmente la libreria che utilizzerete maggiormente.

FOUNDATION. FRAMEWORK

Objective-C disponibile nellSDK, chiamata NSMutableDictionary. La classe NSMutableDictionary (e la sua versione statica, NSDictionary) unaltra delle classi rese disponibili dalla libreria Foundation.framework, e permette di creare istanze in cui viene inserito uno o pi valori con relative chiavi (ogni coppia viene definita come entry), tali chiavi svolgono quindi il compito di identificare univocamente un oggetto tra quelli presenti allinterno dellNSDictionary, consentendone laccesso in maniera estremamente intuitiva. Per realizzare un oggetto di tipo NSMutableDictionary necessario inizializzarlo con una serie di chiavi e di valori a queste associati, in corrispondenza uno-auno, raggruppati allinterno di due array (di tipo NSArray); gli stati possibili saranno fatto, da fare, dimenticato, mentre il testo, ovviamente, varier a seconda dellazione da effettuare. Scegliere tra NSMutableDictionary e la sua controparte immutabile NSDictionary segue lo stesso procedimento decisionale utilizzato per NSArray; dal punto di vista delle prestazioni sicuramente un approccio pi pesante rispetto alla realizzazione di una classe derivata da NSObject, ma in questo contesto stato un pretesto per introdurre queste strutture dati e presentare ulteriori classi fornite nellSDK.
NSArray *keys = [NSArray arrayWithObjects:@"action",@"status",nil ]; NSArray *values = [NSArray arrayWithObjects:@Chiamare Simone,@Da Fare,nil]; //Definiamo e inizializziamo il dizionario NSMutableDictionary *myDict = [[NSMutableDictionary alloc] initWithObjects:values forKeys:keys]]

questi sette indici porremo un NSMutableArray, che ci consentir di variarne il numero di elementi, i nostri task, rappresentati come stato detto da istanze di NSMutableDictionary, nella maniera pi veloce possibile. Dichiariamo giorniSettimana quale array statico prima del metodo viewDidLoad, e procediamo inizializzandolo:
NSArray *giorniSettimana; - (void)viewDidLoad { [super viewDidLoad]; NSLog(@"viewdidload"); self.title = @"ToDo List"; giorniSettimana = [[NSArray alloc] initWithObjects:[NSMutableArray array],[NSMutableArray array],[NSMutableArray array],[NSMutableArray array],[NSMutableArray array],[NSMutableArray array],[NSMutableArray array],nil ]; //Inseriamo nel giorno di domenica (indice 0) una serie di azioni [self insertAction:@"Chiamare Simone" withStatus:@"da fare" inDay:0]; [self insertAction:@"Comprare Latte" withStatus:@"dimenticato" inDay:0]; [self insertAction:@"Registrare Telefilm" withStatus:@"fatto" inDay:0]; //Inseriamo nel giorno di marted (indice 2) una serie di azioni [self insertAction:@"Palestra" withStatus:@"da fare" inDay:2]; [self insertAction:@"Inviare Email" withStatus: @"non fare" inDay:2]; [self insertAction:@"Chiamare Gianni" withStatus:@"dimenticato" inDay:2];

In questo modo myDict conterr due coppie, una con la chiave status, che assumer in questo caso il valore Da Fare, mentre laltra Chiamare Simone associata alla chiave action. Per accedere ai valori associati alle chiavi presenti in myDict baster utilizzare il metodo valueForKey:
//otteniamo lo stato associato (fatto, da fare,dimenticato) NSString * myStatus = [myDict valueForKey:@"status"]; //otteniamo il task associato (@Chiamare Simone) NSString * myAction = [myDict valueForKey:@"action"];

Ovviamente, un NSMutableDictionary (come anche NSDictionary) pu contenere qualunque oggetto Objective-C e un numero teoricamente infinito di coppie chiave-valore.

POPOLARE LA TABELLA
Per popolare il nostro elenco dovremo prima creare larray che conterr le giornate, poich sar immutabile utilizzeremo un NSArray di sette indici (si ricorda accessibili da 0 a n-1), allinterno di ognuna di

Poich, come abbiamo affermato precedentemente, NSArray una struttura dati immutabile, dovremo impostarne i contenuti in fase di inizializzazione, ci avviene invocando il metodo iniWithObjects. Questultimo accetta un array di oggetti, terminato dal simbolo nil: inserendo sette istanze di NSMutableArray abbiamo cos reso questa struttura dinamica nei contenuti. Ci sarebbero state differenze sostanziali se avessimo utilizzato un NSMutableArray per lelenco dei giorni? Non in questo caso e non in termini di codice, ma sicuramente in termini di prestazioni, quando si inizia a lavorare su strutture dati molto pi popolate buona norma pensare a queste semplici ottimizzazioni che spesso possono rendere meno oneroso in termini di risorse e prestazioni il vostro software. Come viene affermato dalla documentazione relativa alla classe, NSArray mantiene un legame strong, forte, con gli oggetti che vengono inseriti al suo interno invocando su di essi un retain, e, venendo a conoscenza di questo comportamento non stato necessario utilizzare la tipica procedura alloc/init ([[NSMutableArray alloc] init]) nella fase di inserimento, abbiamo infatti utilizzato il convenience constructor, un
h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

/Dicembre 2009 G 48 /Novembre 2009

45

La gestione delle tabelle MOBILE La gestione delle tabelle negli applicativi Apple iPhone negli applicativi Apple iPhone iPhone programming

costruttore che non effettua alloc/retain sullistanza creata, e che per tale motivo porter allautorelease dei singoli oggetti, ma, poich questi riceveranno un retain non saranno deallocati. Se avessimo utilizzato alloc+init, ogni oggetto avrebbe avuto un retainCount pari a due, uno per linizializzazione e uno dovuto allinserimento allinterno dellarray, impedendo quindi la completa deallocazione dellarray quando su di questo richiameremo il metodo release (gli oggetti al suo interno avranno un retainCount pari a uno invece che zero, come richiesto per la completa deallocazione della struttura dati). Ora che abbiamo creato un array di sette indici, ognuno contenente un NSMutableArray, realizziamo un semplice metodo per inserire allinterno dei giorni i task (che rappresentiamo con NSMutableDictionary):
(void)insertAction:(NSString *)action withStatus: (NSString *)status inDay:(int)day { NSArray *values = [NSArray arrayWithObjects:action,status,nil]; NSArray *keys = [NSArray arrayWithObjects:@"action",@"status",nil ]; [((NSMutableArray *)[giorniSettimana objectAtIndex: day]) addObject:[[NSMutableDictionary alloc] initWithObjects:values forKeys:keys]]; }

todo abbastanza intuitivo: vengono passate due stringhe che contengono il task e il suo stato, infine lindice del giorno da utilizzare (anche in questo caso si potevano usare i #define per rendere il codice pi leggibile allinterno di viewDidLoad); viene prelevato loggetto di giorniSettimana presente in posizione i-esima, effettuato il casting a NSMutableArray per consentire al sistema di code completition di mostrarci solo i metodi coerenti con tale classe (infatti objectAtIndex restituisce unistanza di tipo id che rende inutilizzabile tale funzionalit, ne avevamo parlato in uno dei primi articoli dedicati alla programmazione iPhone) e inserito al suo interno un NSMutableDictionary contenente le due coppie con chiavi action e status.

AGGIORNAMENTI
Cosa ci resta da fare? Per come abbiamo strutturato i nostri dati identificheremo con le sezioni i singoli giorni della settimana, mentre le righe saranno i singoli NSMutableDictionary in esse presenti. Prima dobbiamo modificare il valore restituito da tableview: numberOfRowsInSection: in modo che fornisca il corretto numero di righe presenti nella sezione/giorno. Baster invocare il metodo count su ogni singolo oggetto del nostro array:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [((NSMutableArray *)[giorniSettimana objectAtIndex:section]) count]; }

LAUTORE
Andrea Leganza Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali su piattaforme e con linguaggi eterogenei. Certificato Adobe ACE Adobe Flex 3 and AIR Certified Expert, e EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto 2 Livello FIN, attualmente impegnato in numerosi progetti multimediali, anche con iPhone, con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it.

Per popolare la tabella baster ottenere le informazioni action/status per ogni NSMutableDictionary presente nelle varie sezioni/giorni:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {... NSString *action = [(NSMutableDictionary *)[((NSMutableArray *)[giorniSettimana objectAtIndex:indexPath.section]) objectAtIndex:indexPath.row] valueForKey:@"action"]; NSString *status = [(NSMutableDictionary *)[((NSMuta

Fig. 5: Ecco come sono state strutturate le informazioni che vogliamo gestire

Non allarmatevi se riceverete un warning che informa della mancata presenza del metodo, baster inserire la sua signature (-(void)insertAction:(NSString *)action withStatus:(NSString *)status inDay:(int)day) seguita da un punto e virgola prima della riga @end allinterno del file rootViewcontroller.h. Poich il numero di utilizzi di questo metodo ridotto, si scelto di non ottimizzare in alcun modo lutilizzo dellarray keys, essendo una struttura che non viene in alcun modo modificata durante il tempo di vita della nostra applicazione, potrebbe infatti essere sostituita utilizzando la direttiva #define, evitando quindi qualunque inizializzazione di istanze (in questo caso una per ogni invocazione del metodo). Il comportamento del meh t t p : / / w w w. i o p r o g r a m m o . i t

bleArray *)[giorniSettimana objectAtIndex:indexPath.section]) objectAtIndex:indexPath.row] valueForKey:@"status"]; cell.textLabel.text = action; cell.detailTextLabel.text = status; return cell; }

CONCLUSIONI
Nel prossimo articolo introdurremo altri concetti e tecniche per utilizzare la tabella, parleremo di cancellazioni, inserimenti e altre operazioni. Andrea Leganza

46

Novembre 2009/ pr49amG g Dicembre i Phone o g r m in

Le tecniche per MOBILEe inserire cancellare righe da una tabella Le tecniche per inserire e cancellare programming tabella iPhone righe da una

GESTIONE DELLE CELLE NELLE TABELLE


N
CD WEB
iphone_tabella146.zip
cdrom.ioprogrammo.it

IN QUESTO ARTICOLO TRATTIAMO DELLE VARIE FUNZIONALIT OFFERTE DALLSDK PER INSERIRE E CANCELLARE UNA O PI RIGHE NELLE TABELLE: ELEMENTO FONDAMENTALE NELLA COSTRUZIONE DELLE INTERFACCE, SIA PER LINPUT CHE PER LA VISUALIZZAZIONE DEI DATI
ellarticolo precedente abbiamo prima mostrato come si popola una tabella utilizzando una struttura statica, un array, e successivamente abbiamo realizzato un metodo per popolarne una utilizzando una struttura dinamica (di tipo NSMutableArray):
-(void)insertAction:(NSString *)action withStatus:(NSString *)status inDay:(int)day { NSArray *values NSArray *keys = [NSArray arrayWithObjects:action,status,nil]; = [NSArray arrayWithObjects:@"action",@"status",nil ]; [((NSMutableArray *)[giorniSettimana object AtIndex:day]) addObject:[[NSMutableDictionary alloc] initWithObjects:values forKeys:keys]]; }

REQUISITI
Conoscenze richieste OOP Software MacOS X 10.5.4 o superiore, XCode Impegno

Poich il metodo che abbiamo mostrato non invocabile, in risposta alla pressione di un pulsante o di un altro componente, non essendo di tipo IBAction (che ricordiamo essere un tipo di metodo che non accetta altri parametri se non il componente grafico che lo ha invocato), dobbiamo percorrere unaltra strada per poter consentire con facilit lesecuzione di una qualunque modifica in risposta allinterazione dellutente: esistono per nostra fortuna svariate tecniche per effettuare ci, una di queste consiste nel modificare la struttura della tabella fornendo campi di testo e pulsanti necessari per modificare i contenuti della struttura dati che utilizziamo, e unaltra invece si realizza presentando un ulteriore UIViewController con relativa UIView, potremo anche presentare un AlertView customizzato; utilizzeremo questa terza procedura per aggiungere un nuovo evento.

abbiamo trattato dei passaggi necessari per arrivare alla scheda di dettaglio di una serie di tabelle. La navigazione una funzionalit fornita dal componente che contiene la nostra tabella realizzata nel file RootViewController.h/.m e nel relativo file .xib, di tipo UINavigationController: se andiamo infatti ad esplorare le variabili presenti nel file nomeprogettoAppDelegate.h possiamo notare che ne presente una di tipo UINavigationController, di tipo IBOutlet, chiamata navigationController, che nel corpo del metodo applicationDidFinishLaunching, utilizzata dal contenitore principale, la variabile window, come fonte della view da mostrare a schermo. In pratica, cosa viene realizzato automaticamente quando si crea un progetto di questo tipo navigation-based? La UIWindow contiene al suo interno una istanza di un UINavigationController, che a sua volta include una tabella; la presenza del navigation controller ci permette con estrema facilit di gestire inserimenti, e ovviamente rimozioni, di un numero virtualmente illimitato di ulteriori UIViewController (nel cui interno sono presenti quindi UIView): in questo modo possibile realizzare un complesso sistema di navigazione, che potremo dire a schede (con il quale intendiamo il binomio UIViewController e relativo UIView). Riguardo lUINavigationController, oltre a gestire la navigazione, consente di inserire dei pulsanti nella sua barra superiore, chiamata navigationBar. Parleremo di questi aspetti successivamente, ma era necessario presentare almeno queste minime informazioni perch lUINavigationController verr utilizzato quando dovremo implementare i pulsanti per effettuare le cancellazioni e gli inserimenti degli eventi.

LA NAVIGAZIONE TRA SCHEDE


Lutilizzo delliPhone principalmente basato sul concetto di consultazione progressiva di informazioni, come stato spiegato quando

NSSTRING
Prima di iniziare doveroso trattare delle stringhe. In uno degli articoli precedenti, dove avevamo trattato della gestione della memoria, e
h t t p : / / w w w. i o p r o g r a m m o . i t

Tempo di realizzazione

i Ph on e p r o g r a m m ing

G 40 /Gennaio 2010

47

Le tecniche per inserire MOBILE Le tecniche per inserire e cancellare righe da una tabella e cancellare righe da una tabella iPhone programming

degli utilizzi di retain/release avevamo evidenziato che le stringhe del tipo NSString non erano soggette a queste regole, avevamo infatti utilizzato negli esempi istanze di NSMutableString; ormai necessario aprire una parentesi e spiegare perch NSString si esenta dalla gestione delle memoria dinamica, soprattutto perch uno dei tipi di dato maggiormente utilizzati negli applicativi iPhone, dove spesso si richiede allutente di compilare uno o pi campi di testo. La spiegazione di estrema facilit: ogni istanza di NSString sempre gestita come una costante rappresentata in Unicode, e non vi quindi modo di modificare il contenuto di una variabile di questo tipo senza che venga, esplicitamente o implicitamente, sostituita con una nuova; se andassimo ad analizzare come vengono gestite due variabili in memoria il cui testo identico, ad esempio prova, scopriremo che queste due puntano alla stessa locazione di memoria, indifferentemente dal momento in cui le abbiamo istanziate, e alla classe in cui tale operazione avviene: lo scopo di tale comportamento ovviamente quello di ottimizzare il consumo di memoria, infatti in questo modo si possono avere migliaia di alias di un solo testo (n alias diversi e una sola variabile in memoria), e non migliaia di istanze con contenuto identico (n variabili in memoria) la cui occupazione quindi di tipo lineare. Quando, durante la fase di compilazione, verranno analizzati i sorgenti, verr avviato un processo che come risultato far in modo che tutte le variabili con stesso contenuto punteranno ad una sola locazione di memoria. Essendo quindi le stringhe delle costanti vengono esentate dalla necessit di retain/release/autorelease e pertanto non ci si deve mai preoccupare di invocare tali metodi per gestirne la memoria. Ultima nota riguardante le stringhe: queste consentono di utilizzare il simbolo di doppia uguaglianza per verificare se i loro caratteri sono identici, anche se loperatore == non ha subito overloading, questo dovuto al fatto che tale operatore verifica, per default, se i due indirizzi puntati da due variabili sono identici, e come appena spiegato ci sempre vero se due stringhe hanno uguali caratteri.
NSString *myString = @prova; NSString *myString2 = @prova; //punteranno in memoria alla stessa locazione if (myString==myString2) {codice sempre eseguito}

puntano sempre a locazioni diverse; in questo caso quindi necessario utilizzare il metodo isEqualTo String.
NSMutableString *myString = [NSMutableString stringWithString:@"prova"]; NSMutableString * myString2 = [NSMutableString stringWithString:@"prova"]; if (myString == myString2) NSLog(@"sono uguali"); //non viene mai verificata come condizione if ([myString isEqualToString: myString2]) NSLog(@"sono uguali con equaltostring");

NOTA

Anche se quindi lecito utilizzare l== con le stringhe di tipo NSString si rischia, in caso di dimenticanza, di utilizzarlo anche con le NSMu tableString, o peggio, con istanze di altri oggetti: il consiglio quindi di usare isEqualtoString per evitare possibili errori. Poich dovremo prelevare del testo da uno o pi campi, non avere questa consapevolezza potrebbe creare problemi inizialmente a quei lettori meno attenti/preparati, inducendoli a passare ore cercando di deallocare queste variabili senza successo ottenendo crash continui.

NSSTRING

Ogni stringa di tipo NSString una costante, quindi non soggetta ad alcuna gestione da parte dellutente e neppure dal sistema di gestione automatica delle risorse; inoltre tutte quelle stringhe che hanno identico valore puntano ad una stessa locazione di memoria.

I POSSIBILI STATI DELLE CELLE DI UNA TABELLA


Ogni cella di una tabella si comporta come un automa a stati finiti, pu passare quindi tra un numero limitato di diverse configurazioni, ognuna delle quali generalmente condiziona il suo aspetto. Successivamente, se coerente con il comportamento atteso, anche il contenuto della relativa riga associata nella struttura dati

Ci non vero quando utilizziamo due NSMutableString, in questo caso luguaglianza non avr lesito sperato perch le due variabili
h t t p : / / w w w. i o p r o g r a m m o . i t

Fig. 1: Due stringhe con ugual contenuto di testo punteranno alla stessa locazione in memoria. Nel nostro esempio: var1 e var2
i Phone pr o g r m in Gennaio 2010/ 41amG g

48

Le tecniche per MOBILEe cancellare righe da una tabella inserire Le tecniche per inserire e cancellare programming tabella iPhone righe da una

NOTA

Lutilizzo di == quale operatore per verificare luguaglianza tra due stringhe di tipo NSString funziona, nonostante non sia soggetto ad overloading, poich verifica se i due indirizzi sono identici. Situazione vera quando due stringhe puntano ad una stessa locazione di memoria, e ci avviene perch stringhe con contenuti uguali puntano alla stessa memoria.

UGUAGLIANZA TRA STRINGHE

collegata: abbiamo uno stato normale durante il quale si effettuano le semplici operazioni di scorrimento e di selezione delle righe (UITableViewCellEditingStyleNone), poi uno stato di modifica che pu essere di cancellazione, oppure di inserimento (esiste anche un terzo, quello di riordino ma che non viene utilizzato in questo modo), il primo prende il nome di UITableViewCellEditingStyleDelete, mentre il secondo UITableViewCellEditingStyleInsert: tali stati sono quindi degli indicatori per consentire di valutare se una determinata cella al momento, visivamente parlando, in uno stato di modifica oppure no; esiste inoltre una propriet della cella, editing, di tipo booleano, il cui scopo indicare leffettivo stato della cella, teoricamente sarebbe infatti possibile impostare una cella con uno stile visivo di modifica e renderla invece non editabile; generalmente editing non viene consultata perch ci si affida al fatto che una cella con un determinato stile ha gi automaticamente impostato tale valore a true o false; se si utilizzano i metodi relativi alla tabella, quelli che vengono generati automaticamente dal wizard, non avrete necessit di valutarla. Infatti noteremo nel prossimo paragrafo che semplicemente verr consultato lo stile visuale e non editing. Generalmente lo stato di cancellazione si evidenzia in due modi: o si presenta con un pulsante rosso, simile ad un segnale di stop, sul lato sinistro della cella che, quando premuto, far comparire un bottone sul lato destro della cella per confermare tale operazione, oppure con solamente un bottone sulla destra; questo secondo aspetto si presenta quando si effettua la cancellazione effettuando lo scorrimento del dito da destra verso sinistra sulla cella desiderata. Molti metodi da utilizzare per gestire tali operazioni vengono generati automaticamente dal wizard del progetto, baster decommentarli e personalizzarli a seconda delle proprie necessit.

successivamente un pulsante rosso sul lato sinistro di ogni riga; dal punto di vista del programmatore, per mostrare il pulsante di edit in alto a destra, baster inserire la seguente riga allinterno del metodo viewDidLoad, ovviamente dopo la chiamata al metodo della classe padre:
[super viewDidLoad]; self.navigationItem.rightBarButtonItem = self.editButtonItem;

In questo modo si impostato il pulsante destro presente nella navigationBar del UINavigationController con uno predefinito, presente in tutti gli UIViewController, il cui comportamento, preimpostato, quello di invocare il metodo (void)setEditing:(BOOL) editinganimated:(BOOL)animated (come esplicitamente descritto nelle documentazione di UIViewController), per la UITableView questo metodo stato gi implementato internamente alla classe e non sar necessario crearlo o modificarlo: provveder ad iterare per tutte le righe e mostrare il pulsante rosso sul lato sinistro di ognuna di queste (se abilitata alla cancellazione). Il pulsante , per informazione, appartenente alla classe UIBarButtonItem e, per supportare la creazione di una cella nel prossimo paragrafo, la utilizzeremo per istanziare un bottone ad hoc (quello nella parte sinistra). Abbiamo ora reso disponibili due modalit di cancellazione, quella multipla, il cui utilizzo termina con lulteriore pressione del pulsante presente a destra nella navigationBar, che assume il testo done successivamente alla prima interazione, e quella singola, scorrimento destra-sinistra del dito sulla cella. Ora necessario abilitare la cancellazione di una o pi celle: cosa che si realizza implementando un metodo per effettuare una o pi cancellazioni. Per supportare questa operazione, baster decommentare, il metodo che riportiamo di seguito:
-(void)tableView:(UITableView*)tableView commitEditingStyle:(UITableViewCellEditingStyle) editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // Delete the row from the data source. //[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

GESTIRE LE CANCELLAZIONI
Gestire la cancellazione di una o pi celle, quando necessario rispondere allinterazione con lutente, si rivela per nostra fortuna, unoperazione estremamente semplice; per quanto riguarda lutilizzatore dellinterfaccia, tale operazione viene invocata automaticamente quando si scorre il dito sulla cella con un movimento da destra verso sinistra, oppure premendo un pulsante, generalmente presente in alto a destra, con label edit, che mostrer

i Ph on e p r o g r a m m ing

G 42 /Gennaio 2010

h t t p : / / w w w. i o p r o g r a m m o . i t

49

Le tecniche per inserire MOBILE Le tecniche per inserire e cancellare righe da una tabella e cancellare righe da una tabella iPhone programming

} }

Decommentando questo metodo si rendono cancellabili tutte le celle; se si volesse creare la logica per proteggerne una o pi si dovr decommentare il seguente metodo, sempre generato dal wizard del progetto, e decidere, a seconda della cella ricevuta, se restituire YES oppure NO:
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { // Return NO if you do not want the specified item to be editable. return YES; }

NOTA

RIFERIMENTI WEB

Tornando al metodo utilizzato per modificare la tabella, analizzando il comportamento di default fornito dagli ingegneri Apple, si noter che viene prima verificato lo stile attuale della cella sulla quale tale metodo stato invocato e, se corrisponde a UITableViewCellEditing StyleDelete, si pu provvedere a cancellare la riga presente nella nostra tabella. La parte di codice che quindi dovremo modificare quella precedente alla seguente riga (che dovrete decommentare se fosse commentata):
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

Creazione dell'account, per scaricare l'SDK e consultare la documentazione:


http://developer.apple. com/iphone/

Fig. 2: Ecco come si presenta la modalit di cancellazione multipla

prima delle cancellazione della cella della tabella:


//Cancellazione dal MODEL [((NSMutableArray *)[giorniSettimana objectAtIndex:indexPath.section]) removeObjectAtIndex:indexPath.row];

Questa chiamata provvede a cancellare visivamente la cella, infatti viene invocata sulla nostra tabella, tableView, ma se non provvederemo a rimuovere anche la relativa entit nella nostra struttura dati avremo unincoerenza tra model (la struttura dati usata, NSMutable Array) e view (le celle della tabella), il nostro software verr terminato con un crash molto esplicativo questa volta: Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (3) must be equal to the number of rows contained in that section before the update (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 1 deleted). Baster quindi aggiungere la seguente riga
h t t p : / / w w w. i o p r o g r a m m o . i t

//Cancellazione dal VIEW [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

Come si nota la gestione della cancellazione si rivelata unoperazione molto semplice: ha comportato solo decommentare il metodo relativo e laggiunta di una sola riga per far riflettere questa modifica anche alla struttura dati.

GESTIRE GLI INSERIMENTI


Esistono svariati metodi per aggiungere una riga nella nostra tabella, come abbiamo detto in precedenza, in questo tutorial utilizzeremo un UIAlertView customizzato; lUIAlertView una UIView che generalmente, in maniera modale, quindi impedendo interai Phone pr o g r m in Gennaio 2010/ 43amG g

50

Le tecniche per MOBILEe cancellare righe da una tabella inserire Le tecniche per inserire e cancellare programming tabella iPhone righe da una

NOTA

Un componente di tipo UIAlertView consente di presentare messaggi, con una o pi possibili scelte, in maniera modale, intercettando quindi linterazione dellutente.

UIALERTVIEW

zione con gli altri componenti, presenta uno o due, bottoni mostrando un messaggio di testo e richiedendo una scelta: un esempio tipico il messaggio che richiede di autorizzare la consultazione della posizione del telefono per una qualche funzionalit di localizzazione che si trova in numerosi applicativi. Noi lo modificheremo per le nostre necessit utilizzando delle funzioni nascoste, non pubblicamente rese disponibili da Apple, che possono essere utilizzate per inserire un qualunque numero di campi di testo (UITextField) al suo interno. Dovremo effettuare tre operazioni, la prima consiste nellinserire un pulsante nella navigationBar sul lato sinistro che ci consenta di inserire una nuova scheda, presentandoci quindi lUIAlertView; la seconda implementare il metodo addItem, che provveder a creare lUIAlert View; la terza sar limplementazione di un secondo metodo in risposta alla conferma della creazione dellevento, successivamente alla pressione dellUIAlertView. Per poter effettuare la prima operazione, quella di visualizzazione del pulsante sulla sinistra, provvediamo aggiungendo le seguenti righe sempre nel metodo ViewDidLoad, immediatamente dopo il comando che abbiamo utilizzato per mostrare il pulsante di cancellazione:
self.navigationItem.rightBarButtonItem = self.editButtonItem;

questo dovuto al fatto che, avendo utilizzato alloc/init, avremo un retainCount di 1, lassegnazione del pulsante di sinistra, come segnalato dalla documentazione, provvede ad effettuare un retain. Quindi, al termine del metodo, se non effettuassimo un release, avremo un retainCount di 2, che non verrebbe mai deallocato, neppure nel caso rilasciassimo il UIViewNavigator, diventando quindi leak. Passiamo ora alla seconda fase:
- (void)addItem:sender { UIAlertView *alert = [[[UIAlertView alloc] initWithTitle:@"Create new Event" message:@"" delegate:self cancelButtonTitle:@"Cancel" otherButtonTitles:@"Submit", nil ] autorelease]; [alert addTextFieldWithValue:@"Sunday" label:@"Day of the week"]; [alert addTextFieldWithValue:@"" label:@"What"]; [alert addTextFieldWithValue:@"" label:@"Status"]; [alert show]}

NOTA

UIBarButtonItem *addButton = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButton SystemItemAdd target:self action:@selector(addItem:)]; self.navigationItem.leftBarButtonItem = addButton; [addButton release];

ULTERIORI APPROFONDIMENTI

Per approfondire questi concetti consultare la guida chiamata Table View Programming Guide for iPhone OS disponibile su sito developer.apple.com o all'interno della documentazione fornita con XCode.

Il codice molto semplice: si crea unistanza di un bottone, come una normale istanza di un oggetto, lo si imposta con uno stile visuale che mostra il simbolo di pi (+) ad indicare linserimento, e si setta come target responsabile della gestione degli eventi il metodo addItem nella stessa classe in cui ci troviamo (self sta proprio ad indicare di utilizzare questa classe). Dopo aver associato il pulsante di sinistra, leftBar ButtonItem, con il nostro bottone, dovremo ricordarci di rilasciare la risorsa,

Limplementazione del metodo addItem crea un UIAlertView con titolo Create new Event, impostando due bottoni, uno di annullamento (con label Cancel) e uno di conferma (con label Submit); entra ora in gioco lutilizzo di un metodo non pubblicato da parte di Apple associato alla classe UIAlertView, tale metodo addTextFieldWithValue, consente di inserire un numero illimitato di campi di testo che consulteremo quando confermeremo la creazione dellevento. Per accedere a questi campi di testo dovremo utilizzare un altro metodo nascosto, [alertView textFieldAtIndex:i], dove ovviamente i lindice, partendo da 0, a cui associato ogni campo, 0 per il campo del giorno della settimana, 1 per quello dellazione, 2 per lo stato dellazione. Per quanto riguarda la pressione dei due pulsanti disponibili implementiamo il metodo alertView: clickedButton AtIndex, invocato automaticamente quando premeremo cancel o submit.
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex { if (buttonIndex != [alertView cancelButtonIndex]) {

i Ph on e p r o g r a m m ing

G 44 /Gennaio 2010

h t t p : / / w w w. i o p r o g r a m m o . i t

51

Le tecniche per inserire MOBILE Le tecniche per inserire e cancellare righe da una tabella e cancellare righe da una tabella iPhone programming

Fig. 3: LUIAlertView che mostra i campi di input

Fig. 4: Il risultato dellinserimento

short day = 0; if ([((UITextField *)[alertView textFieldAtIndex:0]) .text isEqualToString:@"Sunday"]) day = 0; else if ([((UITextField *)[alertView textFieldAtIndex :0]).text isEqualToString:@"Monday"]) day = 1; else if ([((UITextField *)[alertView textFieldAtIndex :0]).text isEqualToString:@"Tuesday"]) day = 2; else if ([((UITextField *)[alertView textFieldAtIndex :0]).text isEqualToString:@"Wednesday"]) day = 3; else if ([((UITextField *)[alertView textFieldAtIndex :0]).text isEqualToString:@"Thursday"]) day = 4; else if ([((UITextField *)[alertView textFieldAtIndex :0]).text isEqualToString:@"Friday"]) day = 5; else if ([((UITextField *)[alertView textFieldAt Index:0]).text isEqualToString:@"Saturday"]) day = 6; [self insertAction:((UITextField *)[alertView textFieldAtIndex:1]).text withStatus:((UITextField *)[alertView textFieldAtIndex:2]).text inDay:day]; [(UITableView *)self.view reloadData]; // //oppure [tableView reloadData] se tableView il nome dellIBOutlet della tabella } }

Come prima operazione verifichiamo che non sia stato richiesto lannullamento delloperazione, baster verificare che buttonIndex, parametro passato dallUIAlertView, corrispondente allindice del pulsante premuto, non sia quello associato al tasto cancel; effettuiamo successivamente una semplice ricerca sul giorno della settimana inserito dallutente nel primo campo di testo (indice = 0), come si pu notare stato utilizzato il casting su [alertViewtextFieldAtIndex:0]. Quando abbiamo identificato il giorno, potremo finalmente aggiungere la nuova azione nel corretto giorno della settimana con il relativo stato. Anche in questo caso avremmo potuto utilizzare loperatore di doppia uguaglianza per verificare quale giorno era stato digitato. Operazione finale, e comunque obbligatoria, consiste nel notificare la View, la nostra tabella, che avvenuta una modifica del Model, il nostro array. Riceverete numerosi warning in fase di compilazione, questo dovuto al fatto che stiamo usando metodi non resi pubblici dalla classe UIAlertView: ricordiamo che non possibile rendere privato un metodo ma solo nasconderlo con alcuni artifici del linguaggio, quindi non c' modo di impedirne effettivamente l'invocazione. Andrea Leganza

LAUTORE
Andrea Leganza Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali, e non, su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certified Expert, e EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multimediali, anche con iPhone, con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it.

h t t p : / / w w w. i o p r o g r a m m o . i t

52

i Phone ogr m in Gennaio 2010/ pr45amG g

Timer e suoni: realizziamo un progetto completo Timer e suoni: realizziamo un progetto completo iPhone programming MOBILE

UNA SVEGLIA DIGITALE PER IPHONE


CD WEB
sveglia.zip
cdrom.ioprogrammo.it

IN QUESTO ARTICOLO MOSTRIAMO COME REALIZZARE UNA SVEGLIA DIGITALE CHE CI AVVISER DI IMPEGNI E SCADENZE IMMINENTI. SAR LOCCASIONE DI APPROFONDIRE I CONCETTI LEGATI ALLA GESTIONE DELLINTERFACCIA E DEL TIMER
messo nelliPhone mantenere unapplicazione in background, si potr utilizzare questa funzionalit di sveglia solo quando la nostra applicazione effettivamente in esecuzione.

IL TIPO DI PROGETTO
Per realizzare questo software utilizzeremo un nuovo tipo di progetto fornito da XCode chiamato Utility Application: gli applicativi che vengono identificati con questo termine sono costituiti da due UIViewController contenenti una singola UIView ciascuno, che si alternano in base alla pressione di un preciso tasto. Il viewcontroller principale viene chiamato automaticamente MainViewController. Il passaggio dalluna a laltra schermata avviene con la pressione del pulsante i nel MainView, e con lutilizzo di quello con label done, posizionato in alto a sinistra nella barra di navigazione di FlipsideViewController. Il sistema utilizzato per effettuare lo switch relativamente complesso e non ne parleremo in questo articolo. Essendo la terza serie di articoli prenderemo per scontate tutte le pratiche necessarie per effettuare la creazione degli IBOutlet e per prelevare le informazioni da tali oggetti. Lintera business logic verr implementata allinterno del MainView Controller, nel quale abbiamo accesso anche ai contenuti del FlipsideViewController.

Fig. 1: Al termine del progetto avremo realizzato una sveglia digitale

REQUISITI
Conoscenze richieste OOP Software MacOS X 10.5.4 o superiore, XCode Impegno

Tempo di realizzazione

n questo articolo mostreremo come realizzare una sveglia digitale. Grazie a questo progetto avremo modo di introdurre alcune classi di estrema utilit: NSTimer, fornita dal framework Foundation, che insieme a UIKit rappresenta le fondamenta di tutte le applicazioni per iPhone, e AVAudioPlayer, presente nel framework AVFoundation, il cui scopo consentire lesecuzione di brani audio di qualunque dimensione. Poich, come risaputo, non per-

IL COMPONENTE UIDATEPICKER
Inserendo un componente del tipo UIDatePicker allinterno della nostra interfaccia grafica, nel FlipsideViewController, forniremo allutente la possibilit di selezionare una data, con una precisione a nostra discrezione; in questo caso impostiamo, tramite Interface Builder, il formato completo (giorno, mese, anno, ore, minuti) settando mode al valore Date and Time, la lingua
h t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 40 /Febbraio 2010

53

Timer e suoni: realizziamo un progetto completo MOBILE Timer e suoni: realizziamo un progetto completo iPhone programming

italiana utilizzando il campo locale, e la precisione, con il campo interval, ad 1 minuto. UIDatePicker fornisce un metodo per ottenere la data selezionata, questa unistanza della classe NSDate e, quando torneremo al MainView provvederemo a memorizzarla in una cartella delliPhone, ed utilizzarla per verificare se lallarme dovr essere eseguito.

tando la data attuale, ottenuta richiedendo il valore del campo date dalla classe NSDate, con quella prelevata dallUIDatePicker:
- (void) handleTimer: (NSTimer *) timer { if ([alarmDate timeIntervalSinceNow]<=0) { //viene eseguito il suono dellallarme } }

CONTROLLO NSTIMER
Un timer un sistema che permette di cadenzare lesecuzione di un metodo con precisi intervalli; ogni timer viene eseguito in un thread distinto. Attenzione, un NSTimer non garantisce che lesecuzione del metodo associato avvenga sempre in un preciso istante, ma che avverr in un istante pari o successivo a quello desiderato, questa limitazione dovuta al fatto che, trovandoci in un ambiente multithread e multiprocess, dovremo condividere le risorse hardware con altri processi in esecuzione. In genere ci avviene utilizzando uno dei cosiddetti algoritmi di scheduling, i quali provvedono a fornire in maniera ciclica sufficiente tempo di calcolo sulla CPU a tutti i processi che ne facciano richiesta; potrebbe capitare quindi che, quando il nostro timer sia in procinto di scadere, avvenga un evento fuori dal nostro controllo che interrompa o rallenti lesecuzione del nostro software per alcuni millisecondi, impedendogli di effettuare in tempo lesecuzione del metodo. La precisione dichiarata di circa 50/100 millisecondi, ma comunque non garantita per le motivazioni appena citate.
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(handleTimer:) userInfo: nil repeats: YES]; - (void) handleTimer: (NSTimer *) timer{ metodo invocato da NSTimer}

timeIntervalSinceNow restituisce un valore positivo, i secondi mancanti alla data attuale, quando alarmDate una data futura, mentre i valori sono negativi se alarmDate ormai trascorso. Abbiamo dovuto utilizzare sia luguaglianza che il simbolo di minore uguale perch, come stato detto, non si pu prevedere se il timer verr eseguito nel preciso minuto in cui dovrebbe scattare levento, o in uno dei successivi.

LA MEMORIZZAZIONE DELLORA DELLALLARME


Dopo aver selezionato una data adoperando lUIDatePicker, provvederemo a memorizzarla in una cartella locale alliPhone e a caricarla ogni volta che il nostro applicativo verr eseguito. Allinterno del telefono esistono alcune cartelle liberamente accessibili e modificabili nelle quali potremo salvare qualunque tipo di informazione: tmp e Documents. La prima deve essere utilizzata per creare e gestire informazioni il cui tempo di vita limitato alla singola esecuzione, mentre la seconda per tutti quei casi in cui un dato deve permanere per diversi avvii dellapplicativo. Esistono svariati metodi per ottenere la corretta posizione di queste cartelle, che sono uniche per ogni software, utilizzeremo il metodo consigliato da Apple quando si realizza un progetto che utilizza la tecnologia Core Data (della quale tratteremo in un prossimo articolo) :
- (NSString *)applicationDocumentsDirectory {

Fig. 2: Il MainView Controller che moster lallarme

Con questo codice abbiamo realizzato un timer che viene avviato ogni secondo (o nei millesimi successivi), che allo scadere dellintervallo esegue il metodo handleTimer e che si ripete allinfinito. Il metodo viene immediatamente avviato dopo la sua esecuzione e subisce un retain automatico. Quando, (e se), non avremo pi bisogno di questa istanza baster invocare su di essa il metodo invalidate, che provveder ad effettuare su di essa un release automatico. Per il nostro scopo questo metodo dovr verificare se giunto il momento per eseguire laudio dellallarme, operazione effettuabile semplicemente confronh t t p : / / w w w. i o p r o g r a m m o . i t

return [NSSearchPathForDirectoriesInDomains(NSDocument Directory, NSUserDomainMask, YES) lastObject]; }

Questo codice restituisce il path, una stringa che rappresenta la posizione completa della cartella Documents. Questo valore potr assumere valori diversi a seconda dellapplicativo, ma anche se vi troverete a utilizzare il tutto nel simulatore. Ii questo caso punter ad una sottocartella definita in: /Users/$NOMEUTENTE/Library/Application Support/iPhoneSimulator/User/Applications/, oppu-

Fig. 3: Il FlipsideView Controller con lUIDatePicker

54

i Phone ogr m in Febbraio 2010/pr41amG g

Timer e suoni: realizziamo un progetto completo Timer e suoni: realizziamo un progetto completo iPhone programming MOBILE

re nel telefono. Per creare un path completo comprensivo del nome del file baster comporre una stringa utilizzando la seguente procedura:
NSString *archivePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"sveglia.cfg"];

-law and a-law MP3 (MPEG-1 audio layer 3 Il formato suggerito dalla documentazione 16bit, little-endian, linear PCM di tipo CAF. possibile convertire i proprio file audio in questo formato utilizzando il tool chiamato afconvert accessibile tramite la finestra di terminale di Mac OS:
/usr/bin/afconvert -f caff -d LEI16 {INPUT}{OUTPUT}

Con questa riga abbiamo realizzato con estrema semplicit un path per un file chiamato sveglia.cfg che verr salvato e caricato quando richiesto. Per verificare se il file sveglia.cfg esiste gi allinterno della cartella Documents, baster semplicemente utilizzare il seguente metodo, fileExistsAtPath, fornito dalla classe NSFileManager, il quale restituir true in caso affermativo, false in caso negativo:
if ([[NSFileManager defaultManager] fileExistsAtPath: archivePath]) { //il file esiste } else { //il file non esiste }

NOTA

Consultare la documentazione online denominata Exception Programming Topics for Cocoa per la gestione delle eccezioni e per lelenco delle eccezioni di default.

ULTERIORI APPROFONDIMENTI

Nel caso di esecuzione multipla viene consigliato lutilizzo del formato IMA/ADPCM (IMA4), mentre per lascolto di file singolarmente suggerisce MP3, ALAC (Apple Lossless), AAC, IMA4. Il primo file che viene eseguito accede direttamente alle risorse hardware, mentre i successivi saranno eseguiti via software. Apple raccomanda nella documentazione di utilizzare questa classe per eseguire qualunque tipo di effetto audio, a meno di avere necessit di gestire in modo distinto i canali stereo, di avere una sincronizzazione precisa, o quando si utilizzano file provenienti da flussi esterni, come avviene ad esempio per le web radio. Apple fornisce numerosi framework oltre ad AVFramework: Media Player framework: per eseguire brani musicali, audio book, podcasts; Audio Toolbox framework: per eseguire audio con precise necessit di sincronizzazione, o analisi o conversione, incluso maggiore controllo sulle fasi di registrazione; Audio Unit framework: per utilizzare plugin audio; OpenAL framework: viene consigliato come la migliore soluzione per eseguire e gestire musiche per i videogiochi, e utilizza OpenAL 1.1. Tornando ora a AVAudioPlayer, nel nostro progetto utilizzeremo un loop audio, il tipico scandire del tempo di un orologio a tempo, e un suono che avviser dellallarme. Prima di effettuare qualunque operazione necessario aggiungere AVFoundation.framework tra i framework che utilizzer il progetto e importarlo allinterno di MainViewController (#import <AVFoundation/AV Foundation.h>). Il codice per eseguire un file audio relativamente breve: prima provvediamo a identificare il path completo della risorsa che ci interessa, (un file mp3 in questo caso), poi creeremo unistanza di AVAudioPlayer, imposteremo il volume, il numero di esecuzioni e lo avvieremo:
NSString *path = [[NSBundle mainBundle] pathForResource:@"clock" ofType:@"mp3"];

Queste informazioni verranno utilizzate per memorizzare la data in cui la sveglia dovr essere avviata in modo da recuperare e utilizzare tale informazione ad ogni avvio.

AVAUDIOPLAYER
AVFramework, (Audio Video Foundation Framework), un framework che consente di eseguire e anche registrare brani audio AVAudioPlayer una delle classi fornite da tale libreria che consente lesecuzione di un singolo file audio (uno per istanza); AVAudioPlayer consente di mettere in pausa e interrompere un audio, avere informazioni sulla sua durata e sulla posizione in cui al momento lesecuzione, consente infine di monitorare i vari livelli di volume assunti dallaudio. Questa classe accetta tutti i formati supportati dalliPhone: AAC HE-AAC AMR (Adaptive Multi-Rate, a format for speech) ALAC (Apple Lossless) iLBC (internet Low Bitrate Codec, another format for speech) IMA4 (IMA/ADPCM) linear PCM (uncompressed)

i Ph on e p r o g r a m m ing

G 42 /Febbraio 2010

h t t p : / / w w w. i o p r o g r a m m o . i t

55

Timer e suoni: realizziamo un progetto completo MOBILE Timer e suoni: realizziamo un progetto completo iPhone programming

AVAudioPlayer player = [[AVAudioPlayer alloc] initWithContentsOfURL:[NSURL fileURLWithPath:path] error:nil]; player.volume = 0.4f; [player prepareToPlay]; [player setNumberOfLoops:-1]; [player play];

Fig. 1: La procedura necessaria per importare il framework

AVAudioPlayer ha un costruttore che accetta un URL, (che in questo caso sar la posizione assunta nel nostro software dalla risorsa chiamata clock.mp3), e una variabile per memorizzare possibili errori (in questo progetto la abbiamo ignorata, impostandola a nil). Il campo volume un float il cui intervallo [0,1], dove con 0 si intende il silenzio, mentre con 1 il massimo valore consentito; il metoto prepareToPlay memorizza laudio in un buffer prima di avviare lesecuzione in modo da evitare interruzioni dovute al caching del file durante il play; setNumberOfLoops: pu assumere un qualunque valore negativo per indicare un loop infinito, 0 per una singola esecuzione, i per (i+1) ripetizioni, inserendo 1 si avranno quindi due avvii successivi del suono. Se volessimo monitorare quando un suono terminato baster aggiungere il metodo audioPlayerDid FinishPlaying: allinterno della nostra classe, appartenente al protocollo AVAudioPlayerDelegate:
- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag { //il suono terminato }

esaustiva quei costrutti che consentono di catturare uno o pi errori generati da una non corretta esecuzione del proprio applicativo. Uneccezione il risultato di un comportamento anomalo, software o hardware, che il programmatore dovrebbe gestire per evitare che il proprio applicativo vada in crash e venga terminato. Per sapere se un metodo o una classe generano una o pi eccezioni necessario consultare la documentazione in linea. La sintassi necessaria per catturare queste eccezioni estremamente semplice, basta inserire il codice che si vuole monitorare allinterno di un blocco di parentesi graffe a cui si antepone @try; con @catch si delimita quella parte di codice che dovr gestire in modo opportuno larrivo delleccezione, ad esempio deallocando una risorsa, o cercando di risolvere il problema; @finally invece un blocco che viene sempre eseguito e che generalmente viene adoperato per effettuare comuni operazioni di release e pulizia delle risorse utilizzate nel blocco @try. Poich il codice racchiuso da @finally viene sempre eseguito risulta estremamente utile perch fornisce un unico punto in cui effettuare le tipiche operazioni di gestione delle risorse, invece di doverle ripetere al termine di @try e di @catch. Esistono numerosi tipi di eccezioni, tutte istanze di NSException, che si distinguono per il nome (il valore del campo name, di tipo NSString) che queste assumono. Oltre a quelle predefinite, ne esistono anche altre presenti in alcuni precisi contesti, e che sono comunque descritte approfonditamente nella documentazione; incapperete allinizio molto spesso in NSRangeException, quando accederete a indici inesistenti di strutture dati (problema che non si presenta analizzandole utilizzando Enumerators e Fast Enumerators in ambienti Thread Safe), e NSInvalidArgumentException, quando passerete parametri non validi ad un metodo.
@try { //codice da monitorare } @catch (tipoeccezione *eccezione) { //nel caso avvenga uneccezione di tipo tipoeccezione viene gestita } @finally { //questo blocco di codice viene sempre eseguito }

NOTA

RIFERIMENTI WEB

Creazione dell'account, per scaricare l'SDK e consultare la documentazione:


http://developer.apple. com/iphone/

GESTITE SITUAZIONI ECCEZIONALI


Chi utilizza linguaggi di alto livello, come JAVA o .NET, ha utilizzato generalmente in maniera
h t t p : / / w w w. i o p r o g r a m m o . i t

Nel caso di pi eccezioni si potranno inserire diversi blocchi @catch, in questo caso viene prima analizzato se leccezione lanciata dentro @try una versione realizzata ad hoc dal programmatore, di nome CustomException e in caso negativo viene confrontata con NSException:
i Phone ogr m in Febbraio 2010/pr43amG g

56

Timer e suoni: realizziamo un progetto completo Timer e suoni: realizziamo un iPhone programming progetto completo MOBILE

@try { } @catch (CustomException *ce) { } @catch (NSException *ne) { } @finally { }

NOTA

Con il termine eccezione si intende un comportamento anomalo del proprio applicativo, generato da cause software o hardware; sempre consigliato gestire le eccezioni per evitare un crash.

ECCEZIONE

Se si volesse gestire in un unico blocco @catch tutte le possibili eccezioni sufficiente catturare quelle appartenenti alla classe NSException che, essendo la pi generica da cui derivano tutte le altre, viene sempre riscontrata. Questa pratica molto comune, ma spesso pi opportuno differenziare i tipi di eccezioni per avere un controllo pi specifico di queste situazioni. Realizziamo un semplice blocco di codice in cui teniamo sotto controllo un metodo fornito dalla nostra classe che accetta come parametro un NSMutableArray che non ha alcun elemento ( infatti stato inizializzato con capacit nulla);
NSMutableArray *anArray = nil; array = [[NSMutableArray alloc] initWithCapacity:0]; @try { [self metodo:anArray]; } @catch (NSException *exception) { } @finally { [anArray release]; }

identificabile con il contenitore principale UIApplicationMain utilizzato allinterno di main.m, dopo di che si verificher un crash del software. Quando viene segnalato nella documentazione in maniera esplicita che linvocazione di un metodo pu lanciare uneccezione, sempre consigliato provvedere a gestire tale evenienza. Nel caso non si desideri gestire uneccezione possibile rilanciarla, girarla alloggetto che contiene listanza in cui ci troviamo, utilizzando @throw:
@try { //codice che lancia uneccezione } @catch(NSException *e) { @throw; // rilancia leccezione }

Nel caso in cui il metodo che abbiamo invocato lanci un qualunque tipo di eccezione siamo in grado di catturarlo e gestirlo come pi riteniamo opportuno. Poich quando si presenta uneccezione i vari blocchi @catch vengono analizzati in sequenza, si procede generalmente inserendo prima quelle eccezioni appartenenti a classi pi specifiche, per poi arrivare alle pi generiche, dove NSException rappresenta la pi generica ( anche possibile utilizzare id come tipo di eccezione pi generale ma non avrete probabilmente mai questa necessit): quando verr rilevata uneccezione nel blocco @try sar cura dellambiente di esecuzione verificare se il primo @catch adatto, oppure se dovr procedere con il prossimo. In caso non ne venga trovato almeno uno, tale eccezione verr inviata allistanza che contiene quella dove avvenuto tale evento e, in caso neppure questa sia in grado di gestirlo, continuer il suo tragitto fino ad un certo punto,

Anche in questo caso verr utilizzata la procedura di ricerca progressiva di @catch in grado di gestirla. Lutilizzo delle eccezioni dovrebbe essere ristretto solo a questo preciso scopo: gestire comportamenti anomali che richiedono un preciso intervento da parte dello sviluppatore, ma anche possibile utilizzarle per rappresentare situazioni non anomale, come la pressione di alcune sequenze di tasti, che darebbero quindi inizio ad una precisa sequenza di eventi: tale comportamento, sebbene possibile, viene sconsigliato da Apple. NSException corredato di un campo chiamato userInfo, un NSDictionary nel quale possibile inserire qualunque tipo di informazione allo scopo di passare alcuni dati al @catch che provveder ad analizzarlo; tale utilizzo verr spiegato in un altro articolo dove mostreremo come realizzare eccezioni customizzate. Per concludere, pu risultare utile mostrare a schermo (in genere viene utilizzato un UIAlertView), o in un log, come nel prossimo esempio mostrato, la causa dellerrore e il tipo di eccezione, NSException, e conseguentemente tutte le classi derivate, fornisce due campi stringa proprio per questo scopo, reason e name:
@catch(NSException *e) { //gestione eccezione NSLog (@EXCEPTION:%@ (%@),e.reason,e.name); }

LA CLASSE NSARCHIVER
La classe NSArchiver fornita da Cocoa in grado di convertire in byte e da byte (in numeh t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 44 /Febbraio 2010

57

Timer e suoni: realizziamo un progetto completo MOBILE Timer e suoni: realizziamo un progetto completo iPhone programming

rosi linguaggi viene adoperato il termine serializzare e deserializzare), istanze di oggetti, scalari, strutture, stringhe e array, ma non consente di effettuare questa operazione per union, void *, puntatori a funzione e sequenze di puntatori. Per quanto riguarda gli NSArray e gli NSDictionary questi devono contenere solo le strutture dati supportate per essere serializzate/deserializzare. La documentazione sufficientemente esplicita a riguardo: Only instances of NSArray, NSDictionary, NSString, NSDate, NSNumber, and NSData (and some of their subclasses) can be serialized. The contents of array and dictionary objects must also contain only objects of these few classes. Per chi non fosse pratico con tali concetti, queste operazioni permettono di memorizzare lo stato di unistanza su un determinato file. Adoperando poi la deserializzazione possibile recuperare questo stato e riutilizzarlo per ripristinare listanza nella stessa configurazione precedente. Utilizziamo questa classe per memorizzare allinterno della memoria del telefono la data impostata dallutente per la sveglia; perch questa procedura funzioni correttamente necessario che la classe da cui deriva listanza su cui utilizziamo NSArchiver sia conforme al protocollo chiamato NSCoding il quale richiede che vengano implementati i seguenti metodi: (void)encodeWithCoder:(NSCoder *)encoder (formale) (id)initWithCoder:(NSCoder *)decoder Solo encodeWithCoder viene esplicitamente richiesto, mentre initWithCoder, che provvede alla deserializzazione, opzionale. Non mostreremo come realizzare il corpo di questi metodi perch la classe che dovremo memorizzare nel telefono, NSDate, gi conforme a questo protocollo. Per capire se una classe supporta out of the box questa tecnica baster consultare la documentazione per verificare se NSCoding appare tra i protocolli supportati. Questa procedura pu essere utilizzata per memorizzare informazioni arbitrarie in maniera estremamente semplice, in un videogioco o in un applicativo, esentando dallobbligo di creare delle proprie strutture dati, come file xml o di testo, per organizzare tali dati. Per memorizzare la data che lutente selezioner adoperando lUIDatePicker baster utilizzare il metodo della classe NSKEyedArchiver archive RootObject: toFile: che consente di effettuare la serializzazione immediatamente. Questo metodo restituisce true in caso di salvataggio
h t t p : / / w w w. i o p r o g r a m m o . i t

completato, false altrimenti.


NSString *archivePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent:@"sveglia.cfg"]; NSString storeResult; if ([NSKeyedArchiver archiveRootObject:alarmDate toFile:archivePath]) { storeResult = @"Configurazione salvata con successo."; } else { storeResult = @"Impossibile salvare il file!"; }

Come si pu vedere, salvare lo stato di unistanza allinterno di un file locale unoperazione estremamente semplice; riottenere invece la data allavvio dellapplicativo comporta lutilizzo di @try/@catch poich, come viene esplicitato dalla documentazione: This method raises an NSInvalidArgument Exception if the file at path does not contain a valid archive. possibile evitare di utilizzare questa pratica verificando prima lesistenza del file utilizzando il metodo fileExistsAtPath fornito dalla classe NSFile Manager, mostrato in un precedente paragrafo.
@try { if ((alarmDate = [NSKeyedUnarchiver unarchiveObjectWithFile:archivePath])){ [alarmDate retain]; .... } @catch (NSException * e) { //Ignoriamo l'eccezione di tipo NSInvalidArgumentException che viene lanciata nel caso sveglia.cfg non venga trovato }

LAUTORE
Andrea Leganza Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali, e non, su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certified Expert, e EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multimediali, anche con iPhone, con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it.

CONCLUSIONI
In questo articolo abbiamo introdotto numerosi argomenti interessanti che consentono di realizzare soluzioni anche molto complesse, giochi compresi. Il progetto annesso alla rivista completo e mostra come si integrano tutti questi concetti. Nei prossimi articoli continueremo questo viaggio nelluniverso iPhone e Objective-C. Buona programmazione. Andrea Leganza
i Phone ogr m in Febbraio 2010/ pr45amG g

58

AccedereMOBILEdellaccelerometro delliPhone ai dati Accedere ai dati dellaccelerometro delliPhone iPhone programming

UN ACCELEROMETRO PER AMICO


LACCELEROMETRO PRESENTE NELLIPHONE UNO DEI COMPONENTI PI EFFICACI NEL CONSENTIRE ALLUTENTE UNINTERAZIONE CON GIOCHI E APPLICATIVI MAGGIORMENTE SEMPLICE E INTUITIVA. VEDIAMO COME INTEGRARLO NELLE NOSTRE APPLICAZIONI

I
CD WEB
accelerometro.rar
cdrom.ioprogrammo.it

REQUISITI
Conoscenze richieste OOP - Objectivive-C Software MacOS X 10.5.4 o superiore, XCode Impegno

Tempo di realizzazione

n questo articolo andremo a realizzare un progetto minimale che ci consentir comunque di approfondire un argomento molto interessante: ricevere, analizzare, e conseguentemente utilizzare i dati provenienti da uno dei sensori pi utili presenti sia nelliPhone che nelliPod Touch: laccelerometro. Questo piccolo componente si trova in un numero sempre crescente di dispositivi, merito anche del successo ottenuto da quando stato inserito nel controller della Wii, il wiimote. NelliPhone questo componente viene principalmente adoperato per automatizzare la rotazione dellinterfaccia grafica di 90 o 180. Con il rilascio della versione 3 del firmware (e del relativo SDK) stata introdotta la funzionalit di shake, di scuotimento, che viene utilizzata per annullare loperazione corrente, oppure per ripetere quella precedente, ma che si presta a centinaia di diverse interpretazioni, dipendenti solo dalla fantasia dello sviluppatore. Questo componente uno di quelli che necessita di un dispositivo reale (non basta il simulatore software) per essere testato: simulare i suoi dati, utilizzando valori casuali o con una certa pianificazione, non sar mai in grado di fornire un feedback accurato che consenta di constatare leffettiva efficacia di come si interpretano tali dati. Laccelerometro un componente elettronico realizzato dalla STMicroelectronics, ed un modello a tre assi, simile nel funzionamento a quello installato nel controller della Wii, il Wiimote, ma in grado di rilevare teoricamente accelerazioni di maggiore intensit (fino a 8g); il sensore in grado di funzionare in due modalit +/-2g e +/-8g, nel secondo caso si ha una perdita di precisione di circa quattro volte rispetto alle misurazioni ottenibili quando in modalit +/-2g, e per tale motivo Apple ha deciso di limitare il range di valori misurabili dal dispositivo per consentire di ottenere una maggiore accuratezza delle misurazioni. quindi possibile ottenere solo valori compresi tra -2g e +2g, con una precisione di circa 0,018g, e purtroppo non permesso modificare attraverso lAPI quale modalit il telefono deve utilizzare (2g o 8g).

INTERFACCIA GRAFICA: LA ROTAZIONE


Quando abbiamo trattato di UIViewController e UITable non abbiamo volutamente accennato a questa possibilit di ruotare linterfaccia grafica in base alleffettiva posizione del telefono perch si deciso di destinare tali informazioni al presente articolo. Queste rotazioni vengono gestite semplicemente consultando una variabile, senza interrogare direttamente laccelerometro. La visualizzazione landscape, orizzontale, estremamente utile in numerosi contesti, non fornire ad un utente tale possibilit pu avere un impatto negativo sullopinione degli utilizzatori del vostro software e quindi, nel caso decidiate di vendere il vostro applicativo, potrebbe portare ad una riduzione delle vendite dovute a feedback non positivi. estremamente semplice abilitare la propria interfaccia in modo che svolga tale operazione in maniera automatica, baster decommentare un metodo, chiamato shouldAutorotateToInterface Orientation, creato automaticamente quando si realizza un UIViewcontroller, o una classe derivata, adoperando il wizard, e configurare a quali tipi di rotazioni tale controller dovr reagire:
// Override to allow orientations other than the default portrait orientation. - (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation { // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait); }

Dopo aver decommentato il metodo analizziamo sia il parametro che riceve che il suo codice interno: interfaceOrientation di tipo UIInterface Orientation, una enum in linguaggio C, i cui possibili valori sono i seguenti:
typedef enum { UIDeviceOrientationUnknown, UIDeviceOrientationPortrait, // Device

i Ph on e p r o g r a m m ing

G 60 / Marzo 2010

h t t p : / / w w w. i o p r o g r a m m o . i t

59

Accedere delliPhone MOBILE Accedere ai dati dellaccelerometro ai dati dellaccelerometro delliPhone iPhone programming

oriented vertically, home button on the bottom UIDeviceOrientationPortraitUpsideDown, // Device oriented vertically, home button on the top UIDeviceOrientationLandscapeLeft, UIDeviceOrientationLandscapeRight, UIDeviceOrientationFaceUp, UIDeviceOrientationFaceDown } UIDeviceOrientation; // Device // Device // Device oriented flat, face up // Device oriented flat, face down } oriented horizontally, home button on the right oriented horizontally, home button on the left {

(UIInterfaceOrientation)interfaceOrientation // Return YES for supported orientations return (interfaceOrientation == UIInterfaceOrientationPortrait || UIInterfaceOrientationLandscapeLeft || UIInterfaceOrientationLandscapeRight);

Quando otteniamo un orientamento di tipo portrait significa che il telefono tenuto in posizione verticale, sia quando il pulsante home del telefono punta verso il basso, che quando il telefono capovolto e tale pulsante punta verso lalto (UpsideDown); landscape ovviamente indica che il telefono tenuto orizzontalmente, con il pulsante home posizionato sul lato sinistro (LandscapeRight), o sul lato destro

Fig. 3: La rotazione viene effettuata automaticamente

(LandscapeLeft); FaceUp e Down vengono utilizzati per notificare che il telefono ha lo schermo verso posizionato in basso, verso il pavimento, o verso lalto. Queste modalit consentono di far reagire la propria interfaccia in base a 6 disposizioni predefinite del telefono, senza necessariamente analizzare i dati che provengono dal telefono e questa modalit di consultazione la pi indicata per modificare la grafica del proprio software. Il metodo viene interrogato dal sistema operativo del telefono che fornir tale parametro e attender un valore booleano che gli consentir di decidere se ruotare o meno linterfaccia automaticamente. La scelta se ruotare o meno dipende da un semplice confronto tra il valore passato come parametro al metodo e i possibili stati del telefono. Decommentando questo metodo non si imposta ancora alcuna rotazione: per tale motivo, se volessimo far ruotare la nostra interfaccia grafica di 90 o 180 gradi, dovremo aggiungere, oltre a UIInterfaceOrientationPortrait, anche le altre modalit che vogliamo facciano scattare il meccanismo di rotazione. Ci si effettua separando le modalit supportare utilizzato OR (simbolo ||):
- (BOOL)shouldAutorotateToInterfaceOrientation:

In questo modo abbiamo automatizzato la rotazione sia quando il software verticale e quando orizzontale in entrambi i versi. Questo tipo di gestione dellaccelerometro lunica che possibile testare parzialmente anche con il solo simulatore: baster, tenendo premuto il tasto mela, operare sui i tasti cursore per far ruotare di 90 a sinistra e a destra linterfaccia, e verificare conseguentemente come reagisce il software. Purtroppo le modalit portraitupsidedown e faceup/down non sono disponibili. La rotazione automatica si propaga anche ai componenti contenuti allinterno del ViewController, UIView, immagini e tutti gli altri inseriti dallo sviluppatore, ma non ha sempre lesito sperato, ci dovuto al fatto che in alcune situazioni necessario adoperare altri strumenti di modifica delle coordinate, che risultano pi adatti (le cosiddette Core Animation Transformations, di cui parleremo in un altro articolo). Prendiamo come esempio unimmagine posizionata nel centro della nostra interfaccia. Quando andremo a ruotare linterfaccia otterremo un effetto simile a quello visibile in Fig.5. La palla da rugby si posizionata in maniera non corretta, un risultato alquanto indesiderato, vedremo in un prossimo articolo come effettuare delle trasformazioni sulle coordinate (le cosiddette trasformazioni affini) degli oggetti presenti nellinterfaccia che permetteranno di ottenere il risultato desiderato.

Fig. 1: La disposizione degli assi e dei versi positivi e negativi dellaccelerometro

I MOTION EVENTS
LSDK 3.0 ha introdotto il concetto di motion events, eventi generati dal movimento. Questi sono scatenati dopo alcune situazioni identificate dallaccelerometro, e non richiedono quindi misurazioni continue da parte dellutente. Gli eventi disponibili sono in numero limitato: motionBegan motionEnded motionCancelled Tutti i tre eventi ricevono due parametri: il primo, di tipo UIEventSubtype, consente di identificare se il movimento stato identificato come appartenente ad una certa tipologia. Al momento lunico
i Phone ogr Marzo 2010/pr61 am m in g G

Fig. 2: La comune visualizzazione verticale di una tabella

h t t p : / / w w w. i o p r o g r a m m o . i t

60

AccedereMOBILEdellaccelerometro delliPhone ai dati Accedere ai dati dellaccelerometro delliPhone iPhone programming

effettivamente utile UIEventSubtypeMotionShake (UIEventSubtypeNone non fornisce alcuna informazione utile) il quale consente di identificare se lutente ha agitato il telefono: se si volesse supportare la possibilit di annullare unoperazione di scrittura di una email ad esempio agitando il dispositivo, basterebbe verificare se la variabile motion assume valore pari a questo tipo di movimento con un semplice if e in caso affermativo agire di conseguenza (cancellando il testo, magari richiedendo con un UIAlert la conferma di tale operazione). possibile emulare lo shake tramite il simulatore adoperando la voce omonima presente nel menu hardware.
- (void)motionBegan:(UIEventSubtype)motion withEvent:(UIEvent *)event { //gestione evento} - (void)motionEnded:(UIEventSubtype)motion withEvent:(UIEvent *)event {//gestione evento } - (void)motionCancelled:(UIEventSubtype)motion withEvent:(UIEvent *)event

ripristinare lo stato del software prima delle modifiche effettuate allinterno di motionBegan. Nel caso in cui il movimento non venga gestito in nessun UIViewController, e la propriet application SupportsShakeToEdit del nostro applicativo sia impostata a YES (accessibile tramite il singleton UIApplication di cui parleremo in un prossimo articolo) verr presentato un menu di scelta tra undo (annullamento) o redo (ripetizione). Questi tre metodi sono meno utili rispetto alla consultazione dei dati forniti dallaccelerometro ma nel caso dellidentificazione del movimento di shake forniscono il modo pi semplice e veloce per supportare questo tipo di interazione.

IL PRELIEVO DELLE INFORMAZIONI


Ora che abbiamo mostrato due situazioni in cui fare uso in maniera implicita delle informazioni provenienti dallaccelerometro, realizziamo il codice necessario per abilitare e leggere i suoi dati in modo esplicito. Ogni applicativo pu accedere a una sola istanza della variabile che fornisce i dati dellaccelerometro, un singleton secondo la terminologia object oriented, tale istanza appartiene al tipo di dato UIAccelerometer; per abilitare la ricezione dei dati si provvede a impostare se stessi, la classe in cui si analizzano i dati, come delegate dellaccelerometro, mentre per disabilitare la ricezione sufficiente reimpostare a nil tale delegate.
int frequency = 50; UIAccelerometer* myAccelerometer; -(IBAction)configurazioneAccelerometro { myAccelerometer = [UIAccelerometer sharedAccelerometer]; myAccelerometer.updateInterval = 1 / frequency; //Impostiamo il delegate myAccelerometer.delegate = self; //Da questo momento verranno inviati i dati dallaccelerometro tramite linvocazione del metodo accelerometer: didAccelerate }

Fig. 4: Il pallone da rugby al centro dellinterfaccia

{ //gestione evento if (event.type == UIEventSubtypeMotionShake) { //se il movimento di shake agiamo di conseguenza } }

NOTA

A questi indirizzi possibile consultare la documentazione dei due modelli di sensori utilizzati nelle diverse versioni delliPhone e delliPod Touch: LIS302DL:
http://www.st.com/stonlin e/products/literature/ds/12 726/lis302dl.pdf

SPECIFICHE HARDWARE

Il parametro event non ha alcuna utilit in questo contesto, viene utilizzato per altri tipi di eventi, quelli touch (il tocco dello schermo) e fornisce informazioni sulle coordinate dei dei vari punti in cui avvenuta linterazione dellutente. Lutilizzo di motionBegan e motionEnded immediato, mentre lutilit di motionCancelled meno intuitiva: questo metodo assume importanza quando un evento identificato come uno di tipo shake dura troppo tempo (oltre un secondo) oppure quando il framework Cocoa Touch richiede linterruzione dellanalisi del movimento, in entrambi i casi questo blocco di codice dovrebbe contenere il codice per

LIS331DL:

http://www.st.com/stonlin e/products/literature/ds/13 951.pdf

Impostando la classe in cui si trova come delegate si dichiara di rispettare il protocollo chiamato UIAccelerometerDelegate, come suggerito in articoli precedenti sempre consigliato dichiarare tale operazione nel file di interfaccia .h:
@interface accelerometroViewController :

Fig. 5: La rotazione automatica si rivelata scarsamente efficace, generando un risultato indesiderato nella maggior parte dei casi

UIViewController <UIAccelerometerDelegate>

La variabile myAccelerometer viene creata richieh t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 62 / Marzo 2010

61

Accedere delliPhone MOBILE Accedere ai dati dellaccelerometro ai dati dellaccelerometro delliPhone iPhone programming

dendo al singleton UIAccelerometer la sua istanza, tramite il metodo sharedAcelerometer. possibile poi prelevare dati a diverse frequenze, in questo caso abbiamo scelto 50Hertz, ci significa il prelievo di una misurazione del sensore ogni 20 millisecondi. Per chi non avesse conoscenza di queste definizioni 1Herz indica una misurazione ogni secondo, 2 Herz 2 al secondo, 10 Hertz 10 al secondo e cos via. Il range di valori consentiti e consigliati da Apple sono: 10-20 Herz: per misurare il verso del telefono 30-60 Herz: per giochi e applicativi in cui richiesta maggiore precisione 70-100 Herz: per misurazione con variazioni di posizione molto elevata I valori verso i 100 Herz non vengono quasi mai utilizzati perch raramente necessaria una tale precisione. Lasse z assumer un valore prossimo a +1 quando il telefono sar appoggiato su una superficie piana, parallela al pavimento, -1 quando lo schermo toccher il piano; y assumer valore -1 quando il telefono sar in posizione portrait con il pulsante home in basso, +1 quando il pulsante home sar rivolto verso lalto; x varr -1 quando lo schermo sar rivolto verso sinistra e +1 verso destra. Tutti i valori compresi tra + e -1 per tutti gli assi verranno assunti quando non si posiziona il telefono perfettamente perpendicolare al piano. La somma delle tre misurazioni deve valere in valore assoluto circa 1. Dopo aver configurato la modalit di ricezione dei dati, notifichiamo alla variabile che siamo in grado di ricevere e gestire le sue informazioni, per tale motivo la informiamo che siamo un delegate; a questo punto necessario implementare il metodo che laccelerometro invocher ad ogni aggiornamento:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration { ...

Nel caso si desideri gestire allinterno di un solo UIViewController queste misurazioni obbligatorio fare in modo che il delegate sia impostato a nil prima che il viewcontroller venga rimosso dallo stack di controller. Se ci troviamo in un sistema di navigazione tra controller, poich altrimenti si rischia il verificarsi di crash casuali dovuti allinvocazione del metodo accelerometer: didAccelerate su unistanza non pi esistente (con il verificarsi della chiamata ad uno zombie quindi). Per visualizzare immediatamente i dati provenienti dalla misurazioni dei tre assi baster creare tre UILabel, impostarle come IBOutlet, e modificare il testo contenuto in base alle rilevazioni:
- (void)accelerometer:(UIAccelerometer *)accelerometer didAccelerate:(UIAcceleration *)acceleration { UIAccelerationValue x, y, z; x = acceleration.x; y = acceleration.y; z = acceleration.z; //Aggiornamento del testo dei tre UILabel xLabel.text = [NSString stringWithFormat:@"%g",x]; yLabel.text = [NSString stringWithFormat:@"%g",y]; zLabel.text = [NSString stringWithFormat:@"%g",z]; }

NOTA

RIFERIMENTI WEB

Creazione dell'account, per scaricare l'SDK e consultare la documentazione:


http://developer.apple. com/iphone/

LAUTORE
Andrea Leganza Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali, e non, su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certified Expert, e EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multimediali, anche con iPhone, con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it.

UNA WII COME TESTER


Anche se allinizio dellarticolo abbiamo dichiarato che non possibile simulare senza il dispositivo reale i dati dellaccelerometro, in realt esistono alcune soluzioni che consentono di bypassare questa richiesta, una di queste consiste nelladoperare un controller della Wii, collegarlo via Bluetooth e fornire tali informazioni attraverso un web server, che verr consultato da codice appositamente realizzato nel proprio applicativo sulliPhone. Questa soluzione comporta sia la realizzazione del codice client nel telefono che di quello server al quale fa riferimento il dispositivo e non quindi unoperazione che un utente non pratico con un altro linguaggio di programmazione (ad esempio C#) potrebbe realizzare in maniera autonoma in tempi relativamente brevi. Inoltre si deve tener conto che il sensore del wiimote invia dati fino a 3g, mentre, come abbiamo precisato in precedenza, nelliPhone si arriva fino a 2g. Andrea Leganza

Avevamo detto che i dati provenienti dallaccelerometro erano di tipo double, ma in questo caso abbiamo adoperato UIAccelerationValue, ebbene questo tipo di dato intercambiabile con double, essendo un suo alias:
typedef double UIAccelerationValue;

Per finire, creiamo un metodo che verr invocato quando si desidera che termini il rilevamento:
-(void) terminaAnalisi { myAccelerometer.delegate = nil; }

h t t p : / / w w w. i o p r o g r a m m o . i t

62

i Phone ogr Marzo 2010/pr63 am m in g G

Gestione dellinterfaccia: i comandi touch touch iPhone programming MOBILE Gestione dellinterfaccia: i comandi

IPHONE: GESTIRE IL MULTI-TOUCH


L

LO SCHERMO MULTITOUCH CONSENTE DI INTERAGIRE CON UN DISPOSITIVO SENZA LA NECESSIT DI FORNIRE INGOMBRANTI TASTIERE. IMPARIAMO A INTERCETTARE LE AZIONI DEGLI UTENTI E A GESTIRLE IN MODO DA NON FAR RIMPIANGERE TASTIERA E MOUSE

evoluzione tecnologica e la conseguente discesa dei prezzi hanno consentito ai produttori di dispositivi mobili di inserire nei propri prodotti uno o pi schermi touch screen: quello che prima era un componente riservato a contesti specializzati, divenuto sempre pi frequente nellelettronica di largo consumo. Nellinterfaccia delliPhone il multitouch consente di interagire in molti modi con i componenti grafici: tocco singolo (single tap) tocchi multipli (multi tap) trascinamento (drag) scrolling verticale (swipe verticale) strisciata (swipe orizzontale)

normali interferenze elettriche, che rappresentano aree di schermo di circa 1 cm di lato (tale la dimensione dellarea che andiamo a toccare effettivamente), su queste informazioni vengono effettuate delle stime per identificare al meglio la regione dove effettivamente lutente voleva interagire, infine vengono calcolate le coordinate del centro di queste aree e vengono inviate, se richiesto, al vostro software, altrimenti vengono ignorate.

CD WEB
iphone149.zip
cdrom.ioprogrammo.it

UIEVENT E UITOUCH
Ogni componente pu rispondere alle interazioni dellutente, purch discenda dalla classe UIResponder, UIViewController, UIView, e conseguentemente le loro classi figlie (in pratica tutti i componenti grafici), derivano da tale padre, e per tale motivo sono pienamente autorizzate a gestire tali eventi. Ovviamente, poich un UIViewController contiene al suo interno un UIView, potr intercettare il tocco prima delle view e gestirlo senza demandare tale operazione ad una delle sue view. I seguenti quattro metodi:
Fig. 1: Una semplice interfaccia grafica per realizzare un sistema di drag and drop

Il termine Swipe, utilizzato nella documentazione iPhone, sinonimo del pi comune scrolling, ed quindi quelloperazione adoperata generalmente per passare da un contenuto allaltro, immagini o video ad esempio, e che consiste in un repentino movimento di uno o pi dita da destra a sinistra o viceversa; In questo articolo vedremo di accedere ai dati che questo dispositivo in grado di fornire allo sviluppatore, e li utilizzeremo per i nostri scopi.

COME FUNZIONA UN TOUCH SCREEN


Il multi-touch presente nelliPhone un insieme di strati costituiti da diversi materiali e in alcuni livelli percorso da componenti elettrici che, quando vengono a contatto con gli strati superiori, identificano tale situazione come la pressione da parte dellutente di una precisa area dello schermo. Gli strati necessari per il funzionamento del multitouch sono interposti tra una copertura antiriflesso, quella che effettivamente lutente tocca con le proprie dita, e il display LCD vero e proprio. Cosa succede quando un utente tocca lo schermo? I vari strati superficiali collidono nei punti in cui avvengono le pressioni, questi eventi generano dei segnali elettrici abbastanza imprecisi a causa di
h t t p : / / w w w. i o p r o g r a m m o . i t i Ph on e p r o g r a m m ing

- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event; - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event

REQUISITI
Conoscenze richieste OOP - Objectivive-C Software MacOS X 10.5.4 o superiore, XCode Impegno

sono quelli, forniti dalla classe UIResponder, che ci consentono di intercettare i tocchi effettuati dallutente: non necessario alcuna operazione aggiuntiva, (come impostare protocolli o delegate per iniziare a gestirli). Lultimo metodo meno intuitivo, viene invocato dal sistema quando si scatenano eventi fuori dal controllo dellutente, come una

Tempo di realizzazione

Aprile 2010/ 55 63 G

Gestione dellinterfaccia: i comandi iPhone programming Gestione dellinterfaccia: i comandi touch touch MOBILE

NOTA

Per una descrizione pi approfondita del funzionamento del touchscreen delliPhone:


http://electronics.howstuff works.com/iphone1.htm

TOUCH -SCREEN

possibile chiamata, una rimozione della view su cui si sta analizzando il tocco (ad esempio per un retain che ne invoca il distruttore), o altra notifica come quello di batteria in esaurimento, che annullano il completamento delle operazioni di tocco iniziate dallutente, in questo metodo generalmente si ripristina il sistema allo stato interno precedente, corrispondente a quello trovato quando stato invocato per la prima volta il metodo touchesBegan. Il primo parametro di questi metodi, NSSet, unistanza di una classe il cui scopo quello di raccogliere in s pi oggetti in maniera non ordinata. In questo caso al suo interno troviamo tutte istanze della classe UITouch, ognuna a rappresentare le diverse dita che toccano lo schermo. La classe UITouch fornisce informazioni riguardo

gestire i dati che provenivano dal sensore, laccelerometro, presente nel dispositivo, e avevamo trattato della famiglia di eventi definiti con la struct UIEventType che raccoglie al suo interno sia gli input di movimento (misurati dallaccelerometro) che quelli di tocco:
typedef enum { UIEventTypeTouches, UIEventTypeMotion, } UIEventType;

Fig. 2: Il contenuto di una istanza di tipo UIEvent

NOTA

Creazione dell'account, per scaricare l'SDK e consultare la documentazione:


http://developer.apple.co m/iphone/

RIFERIMENTI WEB

la posizione attuale del tocco (un punto di tipo CGPoint e coordinate (x,y) ottenuto con la chiamata al metodo (locationInView:), quella precedente (previousLocationInView:), il numero di pressioni ripetute (propriet tap) del dito sullo schermo ed effettuate in breve sequenza (propriet tapCotunt), la view in cui questo stato identificato (propriet view), listante temporale in cui tali coordinate sono cambiate (propriet timestamp, utile per misurare la durata di un movimento, ad esempio), e la fase del tocco (propriet phase), informazione che consente di identificare lo stato del tocco: UITouchPhaseBegan, UITouchPhaseMoved, UITouchPhaseStationary, UITouchPhaseEnded, UITouchPhaseCancelled, Quando ci troviamo in un progetto in cui non ci interessa se uno o pi dita toccano lo schermo, ma vogliamo sapere la posizione di uno qualsiasi dei tocchi, baster invocare il seguente metodo, reso disponibile dalla classe NSSet, allinterno di uno dei metodi touches*:
UITouch *touch = [touches anyObject];

Il secondo parametro di tutti i metodi touches* di tipo UIEvent e assumer come tipo ovviamente il valore UIEventTypeTouches. Listanza appartenente alla classe UIEvent rappresenta quindi lo stato attuale delle dita che sono a contatto con lo schermo, e consente di identificare univocamente le singole istanze di UITouch e come variato il loro stato. Per ottenere i tocchi relativi ad una determinata view basta invocare allinterno di uno dei metodi touches* su tale UIEvent il metodo touchesForView: Come riesce il dispositivo a identificare in quale UIView viene effettivamente effettuato il tocco con una certa coordinata? Questa ricerca avviene semplicemente effettuata in maniera iterativa: si parte dal contenitore principale, che racchiude in se tutti gli oggetti visuali del nostro applicativo, la UIWindow, e si procede scendendo tra gli UIViewController e le rispettive UIView effettuando su di queste il metodo pointInside:withEvent. Tale procedura prosegue per ogni UIView che restituisce YES a tale chiamata, fino a raggiungere lultima UIView che diventer la responsabile della gestione e dellinterpretazione del tocco: tale sequenza di view e viewcontrollers prende il nome di responder chain, catena di risposta. Nel caso lultima view non fosse configurata per gestire il tocco il

il quale restituisce unistanza in modo pseudocasuale (non detto che sia sempre diversa per ogni invocazione successiva); ovviamente se esiste un solo tocco anyObject restituir sempre questo. Nellarticolo precedente avevamo parlato di come

Fig. 3: La responder chain delliPhone

64 G 56 / Aprile 2010

h t t p : / / w w w. i o p r o g rra m m o . ig i Phone pr o g am m in t

Gestione dellinterfaccia: i comandi touch touch iPhone programming MOBILE Gestione dellinterfaccia: i comandi

metodo di ricerca risalir tra le UIView (e i rispettvi controllers) che la contengono in cerca di una in grado di reagire a tale operazione; nel caso peggiore, quello in cui si ritrovasse di nuovo nellUIWindow, tale evento verr ignorato.

OS presente una logica in grado di monitorare e analizzare diversi tipi di interazioni, sia in base al numero che alla direzione del movimento effettuato dalle dita che in quel preciso momento sono a contatto con lo schermo. La sequenza tipo di un evento di tipo singolo tocco costituito dai seguenti passi: 1- un dito tocca lo schermo; 2- il dito viene mosso mantenendolo a contatto con lo schermo (opzionale) 3- il dito viene rimosso Se il movimento, (il secondo passo descritto nella lista), unoperazione opzionale, la prima e la seconda avvengono obbligatoriamente in un intervallo di tempo che pu essere pi o meno breve, nel caso in cui si effettui la tipica operazione di tap, termine che sta ad indicare la pressione singola o pi volte delle dita, siamo in grado in maniera semplice di ottenere il numero di questi tocchi senza necessariamente dover utilizzare una variabile per contabilizzarli. La sequenza tipo di un evento multi tocco costituito dai seguenti passi: 1 - un primo dito tocca lo schermo; 2- un secondo dito tocca lo schermo (il numero delle dita pu essere superiore a due); 3 - uno o entrambi effettuano un movimento; 4 - in successione vengono rimossi entrambi

ABILITARE E DISABILITARE LINTERAZIONE


Nel caso in cui non si desideri che una view riceva uno o pi eventi dovuti al tocco da parte dellutente possibile disabilitare tale interazione in diversi modi attraverso lutilizzo di propriet o metodi forniti dalla classe UIView:

Configurandola come trasparente (ex: myUIView.alpha=0.0); Configurandola come nascosta (ex: myUIView.hidden=YES); Impostando la sua propriet userInteraction Enabled a NO: (ex: myUIView.userInteraction Enabled=NO);

Nel caso si decidesse di disabilitare completamente linterazione per lintero applicativo, ad esempio quando si effettuano delle animazioni sarebbe sufficiente invocare prima di tale situazione beginIgnoringInteractionEvents e successivamente endIgnoringInteractionEvents sul singleton UIApplication ( unistanza di una classe unica durante tutta la vita dellapplicativo) nel seguente modo:
[[UIApplication sharedApplication] beginIgnoringInteractionEvents]; //Eventi che non richiedono interazione con lutente [[UIApplication sharedApplication] endIgnoringInteractionEvents];

Fig. 5: Una tipica situazione di sequenza di eventi

Per default i tocchi multipli non vengono gestiti automaticamente, sono perci ignorati e vengono passate allapplicativo solo quelle informazioni relative al primo tocco rilevato; per abilitare il multitouch necessario invocare nel proprio codice, quando necessario, il metodo multipleTouchEnabled sulla UIView che dovr gestirli. Se si desidera fare in modo che solo una UIView risponda a tutti gli eventi si dovr impostare la sua propriet a exclusiveTouch a YES.

SINGLE-TAP E MULTI-TAP
Come stato detto precedentemente possibile conoscere con estrema facilit il numero di tocchi effettuati in un breve intervallo di tempo dallutente, basta consultare la propriet tapCount, in genere ci viene fatto allinterno del metodo touchesEnded (quando uno o pi dita vengono rimosse quindi).
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { for (UITouch *touch in touches) { if (touch.tapCount == 1) {

Fig. 4: I due checkbox posizionati in basso consentono di cambiare il comportamento predefinito della view attraverso Interface Builder

LE SEQUENZE DI TAP
Multitouch non significa solo gestire i tocchi delle dita contemporaneamente senza identificare la sequenza con cui questi sono avvenuti, nelliPhone
ih t t p : / /p rw w. i o pm inga m m o . i t Ph on e w o g r a m r o g r

Aprile 2010/ 57 65 G

Gestione dellinterfaccia: i comandi iPhone programming Gestione dellinterfaccia: i comandi touch touch MOBILE

//do something } else if (touch.tapCount == 2) { //do something //do something} //etc tapCount>3 } } } else if (touch.tapCount == 3) {

struct CGPoint { CGFloat x; CGFloat y; }; typedef struct CGPoint CGPoint;

IDENTIFICHIAMO E GESTIAMO LO SWIPE


Abbiamo detto che con il termine swipe si identifica lo scorrimento orizzontale/verticale di uno, o pi dita. Per supportarlo allinterno delle proprie UIView (o UIViewController) necessario effettuare alcuni calcoli che identifichino se lo spostamento delle dita dovuto ad un movimento volontario oppure ad un semplice tremolio del dispositivo in mano allutente; per fare ci si deve misurare in un intervallo di tempo come variano le coordinate del tocco e, nel caso tale delta sia superiore o uguale ad un valore predefinito, si pu ritenere con una relativa certezza che lutente stia effettivamente effettuando tale operazione. Vista linaccuratezza del movimento delle dita in una delle due direzioni, opportuno considerare come swipe qualunque movimento che comporti uno spostamento allinterno di un ipotetico rettangolo , il cui centro geometrico dato dalla coordinata del primo tocco. Studiamo il caso di swipe orizzontale. Se il movimento verticale (sia verso lalto che il basso) superer una costante predefinita, MASSIMODELTAVERTICALE, il cui valore espresso in pixel, non identifichiamo tale movimento come uno swipe. In tale situazione probabile che lutente voglia effettuare o uno scrolling in direzione obliqua oppure in orizzontale, ma per qualche motivo non riuscito ad effettuarlo in maniera corretta. Di conseguenze se il movimento orizzontale non sar maggiore di un certo numero di pixels, espresso dalla variabile MINIMODELTAORIZZONTALE, allora probabile che il movimento identificato non sia volontario e lo ignoreremo. Variando tali estremi potremo decidere in che modo reagire ai diversi movimenti , forzando lutente ad effettuare gesti molto precisi oppure consentendogli una maggiore libert.
#define MINIMODELTAORIZZONTALE 8 //minimo spostamento orizzontale in pixel #define MASSIMODELTAVERTICALE 6 //massimo spostamento verticale in pixel

I valori che CGPoint potr rappresentare quando toccheremo lo schermo con un dito potranno appartenere a qualunque punto di coordinate (x,y), dove x compreso tra 0 e 320 e y tra 0 e 480 (0,0 bordo alto a sinistra del telefono, 320,480 basso a destra), dovute alla risoluzione dellLCD delliPhone (CGPoint pu assumere comunque valori superiori o inferiori a questi intervalli ma ovviamente rappresentano posizioni esterne allLCD e non visibili allutente). Per determinare se il movimento contenuto allinterno dellarea utile, effettuiamo semplicemente il calcolo della differenza tra la coordinata precedente, misurata allinterno del metodo touchesBegan, e quella rilevata successivamente allinterno di touchedMoved: in valore assoluto, adoperando la funzione C fabsf:
CGPoint startTouchPosition; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; startTouchPosition = [touch locationInView:self]; } - (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint currentTouchPosition = [touch locationInView:self]; if (fabsf(startTouchPosition.x currentTouchPosition.x) >= MINIMODELTAORIZZONTALE && fabsf(startTouchPosition.y - currentTouchPosition.y) <= MASSIMODELTAVERTICALE) { if (startTouchPosition.x < currentTouchPosition.x) { //swipe a destra } else {//swipe a sinistra} }} - (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { //azzeriamo la posizione iniziale startTouchPosition = 0.0;} - (void)touchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { //azzeriamo la posizione iniziale se avvenuto un evento che ha annullato loperazione startTouchPosition = 0.0;}

Fig. 6: Una tipica situazione di sequenza multipla di eventi

Iniziamo memorizzando la posizione del primo tocco nella variabile chiamata startTouchPosition: di tipo CGPoint, una semplice struttura C

Quando verr alzato il dito, sar sufficiente azzerah t t p : / / w w w. i o p r o g r a m m o . i t i Phone pr o g r am m in g

66 G 58 /Aprile 2010

Gestione dellinterfaccia: i comandi touch touch Gestione dellinterfaccia: i comandi

iPhone programming MOBILE

re la posizione iniziale per essere di nuovo pronti a studiare i tocchi successivi. possibile quindi, variando i due valori di delta predefiniti, creare aree verticali per supportare lo scrolling verticale, come avviene ad esempio per le UITableView o le UIScrollView.

GESTIRE IL TRASCINAMENTO
Loperazione di drag&drop, si rivela meno complicata rispetto a quella di swipe: baster infatti identificare quale UIView stata toccata e muoverla di conseguenza, in risposta agli spostamenti del dito. Esistono due metodi principale per gestire tale operazione: gestire tale comportamento allinterno del UIViewController, che provveder a spostare la UIView gestire tale comportamento allinterno della UIView stessa, creando una classe figlia di UIView. Nel nostro progetto abbiamo deciso di gestire linterazione allinterno del viewcontroller: il pregio di questo approccio dovuto alla possibilit di identificare possibili sovrapposizioni tra le view e agire di conseguenza (ad esempio realizzando un semplice algoritmo di analisi delle collisioni come viene fatto per i giochi) e quindi dovremo inserire i vari metodi touches* nel suo file di implementazione (.m). Creiamo quindi il nostro progetto, di tipo View-Based Application. Creiamo due IBOutlet (ricordate di chiamare su entrambi il metodo release nel metodo dealloc allo scopo di evitare leaks), di nome red e green in questo esempio, allinterno del file di interfaccia (.h) della classe chiamata progettoViewController:
@interface progettoViewController : UIViewController { IBOutlet UIView *red; IBOutlet UIView *green; } @end

Apriamo con interface builder il file chiamato nomeprogettoviewcontroller.xib e trasciniamo allinterno della view principale due ulteriori UIView, ridimensioniamole a nostro piacimento, e impostiamone i colori in modo da poterle distinguere visivamente. Premiamo il tasto destro del mouse sulla voce Files Owner e, tramite la solita operazione di trascinamento dei due IBOutlet, colleghiamoli con le due UIView appena create. Ora non ci resta che implementare i soliti metodi per gestire loperazione di trascinamento; per semplicit realizziamo solo il codice relativo allevento di movimento che invoca di conseguenza il metodo touchesMoved: withEvent, gli altri in questo caso non sono necessari.
- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *theTouch = [touches anyObject]; CGPoint newPosition = [theTouch locationInView:self.view]; if ([self.view hitTest:newPosition withEvent:event] == red){ red.center = newPosition; } else if ([self.view hitTest:newPosition withEvent:event] == green){ green.center = newPosition; } }

Fig. 7: Il sistema di coordinate delliPhone

LAUTORE
Andrea Leganza Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali, e non, su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certified Expert, e EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multimediali, anche con iPhone, con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it.

Il funzionamento abbastanza intuitivo: prima otteniamo la posizione del tocco, rappresentata dal solito CGPoint, successivamente dobbiamo decidere se questo punto occupato da uno dei nostri due UIView: Per effettuare tale operazione interroghiamo la view principale, self.view, quella che contiene le due UIView figlie, invocando su di questa il metodo hitTest; hitTest un metodo che restituisce come risultato quale UIView si trova in un determinato punto: baster quindi verificare che questa corrisponda alla view chiamata red o green, per decidere su quale stia avvenendo il tocco. Per spostare la view selezionata baster cambiarne il centro, utilizzando la propriet center (sempre di tipo CGPoint), attribuendole valore pari alla posizione del tocco. Una soluzione alternativa al codice proposto per venire a conoscenza di quale view al momento toccata consiste nellutilizzare il metodo touchesForView: fornito da UIEvent. Abbiamo cos mostrato i vari modi di interpretare linterazione dellutente in base al numero ed al tipo di touch. Andrea Leganza

Fig. 8: Loperazione di associazione tra IBOutlet e UIView

h t t p : / / w w w. i o p r o g r a m m o . i t i Ph on e p r o g r a m m ing

Aprile 2003/ 59 Novembre 2010/ 59 67G G

Realizziamo un feed reader MOBILE per iPhone Realizziamo un feed reader per iPhone iPhone programming

UN RSS READER SU IPHONE


L
evoluzione apportata dal web negli ultimi due decenni nel campo della comunicazione, ha reso possibile laccesso a migliaia di fonti informative, eterogenee sia in termini di contenuti ma anche di presentazione delle informazioni. Verso la fine del millennio maturata la necessit di definire un formato unico che consentisse linterscambio di tali dati tra software e computer eterogenei ma che fosse anche facilmente leggibile ed analizzabile da un essere umano: da questa realt ha preso forma lXML.
<autore>Andrea</autore> <titolo>Comprare...</titolo> <foto url=rete.png width=320

DISTRIBUIRE INFORMAZIONI ATTRAVERSO LE NOSTRE APPLICAZIONI PU ESSERE MOLTO PI SEMPLICE ADOPERANDO LA CLASSE NSXMLPARSER. SAREMO COS IN GRADO DI RACCOGLIERE E MOSTRARE I CONTENUTI DEGLI RSS CONSUMANDO POCHISSIMA BANDA

<corpo>Comprare cavo di rete 10-1001000</corpo> height=480 /> </nota> <nota> </nota> </note>

CD WEB
iPhone_12.zip
cdrom.ioprogrammo.it

COM FATTO UN DOCUMENTO XML


Allinterno di un file XML prendono posto uno o pi tag, entit testuali chiamate elementi, delimitate dal simbolo minore (<) e maggiore (>) che contengono le informazioni associate a tale marcatore. Ogni tag pu essere del tipo <tag>contenuto</tag> oppure <tag />, pu contenere al suo interno altri tag in numero teoricamente illimitato, e pu inoltre avere dei valori associati, chiamati attributi che prendono posto subito dopo il nome del tag, prima del simbolo >; ecco un esempio in cui vengono adoperate tutte queste caratteristiche:
<?xml version="1.0"?> <note> <nota> <autore>Andrea</autore> <titolo>Ricordarsi di...</titolo> <corpo>Ricordarsi di inviare una mail. </corpo> <foto url=andrea.png width=160 height=220/> </nota> <nota>

La struttura di un file XML a discrezione dello sviluppatore, anche per quanto riguarda i nomi dei tag. possibile impostare delle regole che consentono di limitare il tipo, il numero e i valori contenuti nei suoi elementi, adoperando DTD o XML Schema. La descrizione qui fatta dellXML estremamente semplificata e le migliaia di risorse disponibili online comprese le decine di libri pubblicati rendono certamente maggiore giustizia a questo formato del W3C. Uno dei maggiori difetti che gli vengono attribuiti la sua prolissit, che ha spinto molti sviluppatori ad optare per JSON (JavaScript Object Notation). Con il proliferare di siti e blog ha preso vita il formato ora conosciuto come Really Simple Sindication, RSS, inizialmente nato con il nome di RDF Site Summary negli uffici di Netscape. LRSS (come il suo concorrente, Atom) non nientaltro che un file XML strutturato con delle regole precise, il cui scopo quello di organizzare e rendere fruibili allesterno di un sito web le informazioni che vengono pubblicate al suo interno senza richiedere la consultazione ripetuta delle sue pagine. Questo formato presente in milioni di siti, anche merito dellutilizzo di piattaforme di blogging e CMS che ne hanno automatizzato la creazione, e licona con sfondo arancione e righe bianche ormai divenuta un simbolo universalmente riconosciuto per identi-

Fig. 1: La visualizzazione dei titoli dei feed

REQUISITI
Conoscenze richieste Objectivive-C Software MacOS X 10.5.4 o superiore Impegno

Tempo di realizzazione

h t t p : / / w w w. i o p r o g r a m m o . i t

68

i Phone ogr m in Giugno 2010/pr71amG g

Realizziamo Realizziamo un feed iPhone programming MOBILE un feed reader per iPhone reader per iPhone

ficarlo. Il pregio di tale formato risiede anche nel fatto che consente di ridurre il traffico dati dei siti, poich un software che adopera RSS per verificare se i suoi contenuti sono stati aggiornati richiede un minore scambio di byte rispetto a quello necessario per effettuare il caricamento completo necessario per visualizzare interamente le pagine web (costituite da HTML, Javascript e immagini). A differenza dellXML, in RSS alcuni campi sono obbligatori e nel prossimo paragrafo avremo modo di approfondire questo discorso. Un aggregatore, o RSS reader, quel software che consente di gestire e consultare tali file RSS. In questo articolo e nel prossimo andremo a realizzare un RSS reader, adopereremo a tale scopo la classe NSXMLParser, presentando i titoli sequenzialmente in una tabella, e mostreremo i singoli contenuti in base alla selezione effettuata. NSXMLParser un parser, una classe dedicata allanalisi di file XML, di tipo SAX, realizzato in Objective-C e disponibile allinterno dellSDK.

posto la descrizione del file, la lingua, la data di pubblicazione, e successivamente si avranno uno o pi tag item che rappresentano le singole notizie. Ogni item pu contenere diversi campi, tra cui il titolo, il link alla pagine completa, la descrizione, la data di pubblicazione. Quelli mostrati sono sono alcuni dei tag disponibili, ma nessuno di questi obbligatorio, a parte title, link e description per quanto riguarda la descrizione del feed, e title o description per il contenuto del singolo item. Consultando le specifiche relative alla versione utilizzata possibile realizzare un feed compliant con tale documentazione, avendo garanzia che sar accettato dalla maggior parte dei feed reader.

SAX E DOM
Quando si adopera un parser per analizzare un file XML/RSS, esistono principalmente due approcci per accedere ai suoi contenuti: uno quello basato su SAX, laltro su DOM. Un parser che adopera SAX, acronimo di Simple API for XML, si basa su un certo numero di metodi (viene detto di tipo event-driven) che vengono invocati quando la libreria incontra determinati elementi del file XML, allinterno di tali metodi quindi cura del programmatore gestire i contenuti incontrati e memorizzarli in una struttura dati che verr poi mostrata allutente; lanalisi del documento quindi di tipo sequenziale, partendo dalla radice del file giungendo fino allultimo ramo. Lapproccio che adopera invece il DOM (Document Object Model, giunto alla versione chiamata Level 3), del tipo tree-based, poich analizza una copia completa della struttura del file XML, la cui struttura appunto ad albero, mantenendola interamente in memoria. Un parser di tipo SAX viene definito streaming parser proprio per il fatto che attraversa e analizza la struttura in sequenza, e si rivela proprio per questa caratteristica come la soluzione migliore quando si analizzano file di dimensioni relativamente grandi; se il proprio obiettivo quello di non occupare eccessiva memoria conviene optare per SAX, mentre si pu scegliere indifferentemente per luno o laltro approccio quando si parsano documenti di modeste dimensioni. Perch allora non scegliere sempre una libreria che adopera SAX? In alcune situazioni necessario avere accesso allintera struttura del file immediatamenh t t p : / / w w w. i o p r o g r a m m o . i t

IL FILE RSS
Ecco un esempio di come pu essere strutturato un RSS versione 2.0:
<?xml version="1.0"?> <rss version="2.0"> <channel> <title></title> <link></link> <description></description> <language></language> <pubDate></pubDate> <lastBuildDate></lastBuildDate> <item> <title></title> <link></link> <description></description> <pubDate></pubDate> </item> </channel> </rss>

Allinterno del tag rss ne contenuto un altro chiamato channel, che presenta prima una serie di campi che descrivono le caratteristiche generali del feed, come titolo, link ad una risorsa web, generalmente questo campo contiene il sito a cui associato il file, trova poi

i Ph on e p r o g r a m m ing

G 72 / Giugno 2010

69

Realizziamo un feed reader MOBILE per iPhone Realizziamo un feed reader per iPhone iPhone programming

te, come avviene per il DTD, adoperato per validare un documento, lXSLT, utilizzato per applicare trasformazioni/conversioni di documenti XML, e XPath, utilizzato per accedere ai contenuti dellXML, e in questi casi il DOM si presenta come la soluzione ideale, sempre a condizione di non avere strutture dati di dimensioni eccessive. Per concludere, citiamo un ultimo pregio dellapproccio SAX: nel caso ci trovassimo nella situazione di dover prelevare un file XML di medie/grandi dimensioni da una sorgente remota (web o intranet) per ottenere una precisa informazione, grazie alla natura stessa di tale tipo di parser potremo interrompere il download della risorsa quando avremo raggiungo il tag desiderato, ci comporter un risparmio di traffico che in alcuni casi potrebbe essere rilevante. Con lapproccio DOM ci non possibile, poich il file deve sempre essere trasferito e memorizzato completamente sul computer locale prima di effettuare qualunque analisi.

aggiunge la possibilit di modificare lXML. TinyXML: libreria realizzata in C di tipo DOM, supporta lettura e scrittura di file XML, ma non XPath, funzionalit implementabile adoperando unaltra libreria associata chiamata TyinyXPath. GDataXML: libreria di tipo DOM che mima lapproccio di NSXMLParser, realizzata in Objective-C, sviluppata da Google, supporta lettura e scrittura di file XML e XPath. Per quanto riguarda la scelta tra le due librerie fornite allinterno dellSDK, NSXMLParser e libXML2, questo dipende dalle dimensioni dei file XML coinvolti, ma anche dalla propria esperienza nel linguaggio C, obbligatorio quando si opta per la seconda libreria; nel caso di indecisione si possono testare le prestazioni delle due librerie sui propri file XML/RSS modificando un progetto fornito allinterno della documentazione presente sia nel sito Apple che allegata in XCode, il cui nome XMLPerformace, raggiungibile nella sezione Related sample code della NSXMLParser Class Reference. Anche se libXML2 ineguagliabile per la velocit con cui analizza un documento resta sempre da considerare la necessit di adoperare il lin-

NOTA

SPECIFICHE XML, RSS 1.X E 2.X

LIBRERIE A CONFRONTO
Nonostante in questo articolo andremo ad utilizzare solo la classe NSXMLParser opportuno sapere che ne esiste unaltra, utilizzabile sia con SAX che DOM, realizzata in linguaggio C e chiamata libXML2 fornita nellSDK (nata in seno al progetto Gnome), il cui utilizzo per si rivela sicuramente meno intuitivo rispetto alla prima, ma le cui prestazioni sono nettamente superiori, spesso di un ordine di grandezza, merito ovviamente del linguaggio a pi basso livello che stato utilizzato. A fianco di queste due soluzioni ne trovano poi posto altre, disponibili nel web che si propongono come unalternativa fornendo un approccio solamente tramite DOM: TBXML: libreria di tipo DOM con approccio leggero (in termini di risorse), realizzata in Objective-C, non effettua la validazione , non supporta XPath, e supporta solo la lettura del file XML, non la modifica. TouchXML: libreria di tipo DOM che mima lapproccio di NSXMLParser, realizzata in Objective-C, supporta XPath ma come TBXML solo per lettura di file XML. KissXML: libreria di tipo DOM che mima lapproccio di NSXMLParser, realizzata in Objective-C, basata su TouchXML, alla quale
h t t p : / / w w w. i o p r o g r a m m o . i t

Per mantenersi aggiornati sulle specifiche riguardanti XML ed RSS, conviene fare affidamento sui siti ufficiali:
www.w3.org/XML/ http://web.resource.org/ rss/1.0/spec http://cyber.law.harvard. edu/rss/rss.html

Fig. 2: Un test effetuato analizzando le top 300 songs di itunes mostra le differenti prestazioni delle due librerie fornite nellSDK

70

i Phone ogr m in Giugno 2010/pr73amG g

Realizziamo Realizziamo un feed iPhone programming MOBILE un feed reader per iPhone reader per iPhone

NOTA

Creazione dell'account, per scaricare l'SDK e consultare la documentazione:


http://developer.apple.com/ iphone

LE RISORSE PER IPHONE

guaggio C, che per alcuni utenti si rivela lo scoglio maggiore. Se invece si vuole adoperare DOM la scelta pu cadere su qualunque delle cinque librerie precedentemente descritte se si vuole solo analizzare il documento XML, mentre il cerchio si restringe in caso si dovesse adoperare XPath oppure fosse necessario modificare il file. Da test empirici risultato che TBXML si rivelata una delle migliori in termini di velocit e di occupazione di memoria, ma per il fatto che molte librerie vengono modificate costantemente questa affermazione potrebbe gi essere invalidata mentre leggete queste righe; comunque per documenti di dimensioni medio-grandi, quando la velocit di analisi del documento di primaria importanza, il suggerimento di adoperare libxml2 (approccio SAX), mentre conviene optare per TBXML per un approccio DOM, il cui pregio in confronto a libxml2 (approccio DOM) ovviamente la possibilit di adoperare lObjective-C. Unultima nota che potrebbe interessare chi desidera analizzare anche pagine HTML con tali parser: con tutte queste librerie possibile analizzare anche pagine web di tipo XHTML o HTML 5, poich entrambi i formati sono applicazioni dellXML.

%@", parseResult?@completato:@errore);

Con queste poche righe di codice abbiamo gi configurato ed avviato il parser. Nella prima riga abbiamo creato un oggetto di tipo NSURL contenente il link al file RSS, nella seconda stata istanziata ed inizializzata una variabile di NSXMLparser, successivamente abbiamo impostato il suo delegate alla classe corrente in modo da ricevere tutti gli eventi che verranno generati dal parser durante la sua navigazione allinterno del file. Infine, invochiamo il metodo parse che d inizio allanalisi del documento e mostriamo il risultato dellevento nella console di debug tramite NSLog. Ora che abbiamo implementato il blocco di codice responsabile dellavvio del parsing apriamo una parentesi sui metodi che il parser invier alla nostra classe, che svolge il ruolo di delegate.

IL FUNZIONAMENTO DI NSXMLPARSER
Utilizziamo un file XML molto semplice, contenente come informazioni la versione, un articolo e una descrizione associata
<?xml version= "1.0" encoding="UTF8"> <articolo autore="Andrea Leganza"> <titolo>Un feed reader per tutti</titolo> <descrizione>Articolo dedicato ai file XML.</descrizione> </articolo>

IL PROGETTO
Creiamo un nuovo progetto, di tipo Utility Application, chiamandolo rssparser, modifichiamo la MainView inserendo al suo interno un UITableView (impostiamo delegate e datasource al view controller Mainviewcontroller.h tramite interface builder), al quale accederemo attraverso un IBOutlet inserito allinterno del file MainViewController.h, che chiameremo rssTable, ricondandoci sempre di invocare il release allinterno del metodo dealloc per evitare leaks. Rimuoviamo il pulsante posizionato automaticamente in basso a destra con licona i perch non necessario in questo progetto. Decommentiamo il metodo viewDidLoad allinterno di MainviewController.c e inseriamo il seguente codice:
NSURL *theURL = [NSURL URLWithString:@ "http://rss.cnn.com/rss/edition.rss"]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:theURL]; [parser setDelegate:self]; BOOL parseResult = [parser parse]; NSLog(@"Risultato del parsing della risorsa :

per mostrare come si comporta unistanza della classe NSXMLParser, descrivendo la sequenza degli eventi inviati al suo delegate, che deve rispettare il protocollo richiesto chiamato NSXMLParserDelegate: 1. Avvio del parsing del documento; 2. Trovato inizio del tag articolo con attributo autore e valore Andrea Leganza; 3. Trovato inizio del tag titolo; 4. Trovati caratteri allinterno del tag titolo: Un feed reader per tutti.; 5. Trovata fine del tag titolo; 6. Trovato inizio del tag descrizione; 7. Trovati caratteri allinterno del tag descrizione: Articolo dedicato ai file XML.; 8. Trovata fine del tag descrizione; 9. Trovata fine del tag articolo; 10. Fine del parsing del documento. I passi 1 e 10 avvengono una sola volta duranh t t p : / / w w w. i o p r o g r a m m o . i t

i Ph on e p r o g r a m m ing

G 74 / Giugno 2010

71

Realizziamo un feed per iPhone Realizziamo un feed reader per iPhonereader MOBILE iPhone programming

te la fase di parsing, mentre quelli di inizio/fine elemento (parser:didStartElement* e parser:didEnd Element*) una volta per ogni tag incontrato; come si nota lanalisi procede andando sempre pi in profondit, per poi risalire quando trova i vari tag di chiusura. Se avessimo inserito una sequenza di n tag articolo, avremo avuto n ripetizioni della sequenza dei passi da 2 a 9. Passiamo ora dalla versione con pseudocodice alla sequenza di chiamate dei metodi effettivamente richiesti al delegate:
1. 1. parserDidStartDocument: parser:didStartElement:namespaceURI: qualifiedName:attributes: //trovato tag articolo 1. parser:didStartElement:namespaceURI: qualifiedName:attributes: //trovato tag titolo 2. 3. parser:foundCharacters: //trovato contenuto per tag titolo parser:didEndElement:namespaceURI: qualifiedName: //fine tag titolo 4. parser:didStartElement:namespaceURI: qualifiedName:attributes: //trovato tag descrizione 5. parser:foundCharacters: //trovato contenuto per tag descrizione 6. parser:didEndElement:namespaceURI: qualifiedName: //fine tag descrizione 7. parserDidEndDocument:

listanza di NSError chiamata parseError i cui codici di errore sono elencati in una legenda presente nella sezione Parser Error Constants della documentazione associata alla classe NSXMLParser.

IL NUMERO DI INVOCAZIONI
La sequenza di eventi descritti nel precedente paragrafo per il file XML mostrato speculare a quella per un file RSS ovviamente con alcune differenze in termini di numeri: in questo caso il numero di invocazioni di parser:didStart Element:... (e del complementare parser:didEndElement:...) nettamente superiore: otterremo infatti una sequenza di eventi dovuta alla parte di descrizione del feed, e successivamente, quando il parser giunger alle notizie, identificate dai tag item, in numero pari a molte decine, anche centinaia. Per valutare il numero complessivo di invocazioni di metodi richieste al delegate senza avviare il software, basandosi solo sulla struttura del file RSS, baster calcolare il risultato della seguente formula: S+(K*2)+ (n*2)+(n*(j*2))+d dove S: costante pari a 2, i due metodi di inizio e fine documento K: costante, numero di tag utilizzati per la descrizione del feed; n: numero di notizie (tag item) j: numero di tag presenti allinterno della singola notizia (tag item); d: numero di invocazioni di foundCharacters: ovviamente possibile calcolare tutti questi parametri inserendo una variabile che contabilizzi il numero di invocazioni di ogni metodo quando si avvia un parsing. Questi calcoli possono risultare utili quando si al contempo consumatore (tramite il software iPhone dellRSS), ma anche responsabile della sua generazione. In tal modo si possono apportare modifiche strutturali per velocizzarne lanalisi, riducendo accapo e caratteri inutili, oppure semplificando la struttura accorpando tag ed adoperando attributi. Nel prossimo articolo tratteremo approfonditamente del prelievo delle informazioni allinterno dei tag e mostreremo la loro descrizione con le informazioni associate. Buona programmazione. Andrea Leganza

LAUTORE
Andrea Leganza Laureato in Ingegneria Informatica, da oltre un decennio realizza soluzioni multimediali, e non, su piattaforme e con linguaggi diversi. Certificato Adobe ACE - Adobe Flex 3 and AIR Certified Expert, e EUCIP Core, appassionato di fotografia, lingua giapponese e istruttore di nuoto FIN, attualmente impegnato in numerosi progetti multimediali, anche con iPhone, con alcune societ nazionali ed internazionali; contattabile su [email protected] o direttamente sul sito
www.leganza.it.

Oltre a questi ne esistono ovviamente molti altri, come quelli scatenati quando il parser incontra caratteri particolari, tipi di informazioni non prettamente testuali (CDATA), commenti, o il DTD; per avere una visione completa di tutti gli eventi che il delegate pu ricevere basta consultare la documentazione relativa a NSXMLParser Delegate. Lultimo metodo che sempre opportuno implementare in aggiunta ai precedenti quello invocato dal parser per identificare e gestire le situazioni di errore, chiamato parser:parseErrorOccurred:. Un errore pu presentarsi per diversi motivi, come tag non chiusi, oppure caratteri non accettati, in pratica in tutte quelle situazioni in cui il parser indeciso sul da farsi perch ha incontrato una struttura non coerente. Allinterno del metodo parser:parse ErrorOccurred si pu decidere come procedere dopo aver interrogato
h t t p : / / w w w. i o p r o g r a m m o . i t

72

Giugno 2010/ o g r am m in g i Phone pr 75 G

MOBILE

iPhone parsing RSS iPhone parsing RSS

iPhone programming

UN RSS READER PER IPHONE


N
CD WEB
iPhone_RSS.rar
cdrom.ioprogrammo.it

ADOPERANDO LA CLASSE NSXMLPARSER, POSSIAMO INTERAGIRE CON I CONTENUTI DI UN FEED RSS. IN QUESTO ARTICOLO MOSTREREMO COME FARE E COME IMPLEMENTARE UNA STRUTTURA DATI PER LA VISUALIZZAZIONE DELLE INFO RICEVUTE
ellarticolo precedente abbiamo trattato in maniera teorica del parsing di feed RSS, descrivendo a grandi linee il formato RSS, ci ha permesso di mostrare come questo sia semplicemente un file XML con una precisa struttura; in un secondo momento sono stati descritti i due tipi di parser forniti nellSDK, DOM e SAX, dei quali abbiamo spiegato pregi e difetti; successivamente siamo entrati nel vivo del corso mostrando il funzionamento del parser NSXMLParser, il quale invoca sequenzialmente alcuni metodi che devono essere implementati dal suo delegate, comportamento che viene definito event-driven. In questa ultima parte mostreremo il contenuto dei metodi del delegate e ne spiegheremo il funzionamento. classe NSXMLParser impostandone come delegate la classe in cui questo codice presente; infine, diamo inizio alloperazione di parsing. Ora che abbiamo dato inizio alloperazione di parsing sappiamo, dallarticolo precedente, che avremo una sequenza di chiamate ai seguenti metodi:
parserDidStartDocument: (1 chiamata) parser:didStartElement:namespaceURI:qualifiedName: attributes: (n chiamate) parser:foundCharacters: (m chiamate) parser:didEndElement:namespaceURI:qualifiedName: (n chiamate) parserDidEndDocument: (1 chiamata)

IL PARSING DEL FILE RSS


Riproponiamo per comodit il metodo viewDidLoad nel quale diamo inizio alloperazione di parsing:
- (void)viewDidLoad { //Allochiamo ed inizializziamo larray che conterr le singole news; notizie = [[NSMutableArray alloc] init];

REQUISITI
Conoscenze richieste Objective-C Software Mac OS 10.5.x o superiore (10.6.x per lSDK 3.x) Impegno

NSURL *theURL = [NSURL URLWithString:@http://rss. cnn.com/rss/edition.rss]; NSXMLParser *parser = [[NSXMLParser alloc] initWithContentsOfURL:theURL]; [parser setDelegate:self]; BOOL parseResult = [parser parse]; NSLog(@Risultato del parsing della risorsa : %@, pars eResult?@completato:@errore); }

ParserDidStartDocument e parserDidEndDocument indicano linizio e il termine della procedura di parsing e generalmente sono adoperati per inizializzare e deallocare se necessario, tutte quelle variabili e strutture dati che verranno utilizzate durante la fase di analisi del documento RSS. parser:didStartElem ent:namespaceURI:qualifiedName:attributes: e parser:didEndElement:namespaceURI:qualifiedName: svolgono invece un ruolo molto importante, infatti ci informano quando stato trovato un tag, aperto (<tag>) o chiuso (</tag>). parser:foundCharacters: viene invocato quando trova del testo tra un tag di apertura e uno di chiusura (<tag>testo</tag>). parser:foundCharacters viene invocato quando il parser incontra del testo allinterno di un tag. Allinterno di questi metodi dovremo popolare una struttura dati apposita con le informazioni ricevute per ogni tag, che andremo poi a inserire allinterno di un array: al termine del parsing troveremo in tale array per ogni suo record tutti i dati associati al singolo tag incontrato. Generalmente loperazione di parsing necessita delle seguenti strutture dati:

una istanza della classe NSMutableString, currenSpieghiamo brevemente il codice appena proposto: abbiamo creato unistanza di NSURL nella quale stato inserito lURL del feed che vogliamo analizzare, successivamente istanziamo un oggetto della tElement, per condividere tra le varie chiamate dei metodi il tag che stiamo analizzando; n istanze della classe NSMutableString; nel nostro caso sono quattro, currentFitle, currentDate, cur-

Tempo di realizzazione

i Ph on e p r o g r a m m ing

48 / Luglio 2010

h ttp ://www.io p r o g r a m m o .i t

73

iPhone programming
rentSummary e currentLink, ognuna conterr il testo associato al relativo tag; una istanza del tipo NSMutableDictionary, con il nome notizia, che conterr tutte le informazioni che verranno trovate per una singola news e associate alle NSMutableString (titolo, data, sommario, link). un array del tipo NSMutableArray, notizie, nel quale andremo ad inserire al termine del parsing di un tag di tipo item lNSMutableDictionary appena popolato; tale istanza quindi alla fine del parsing conterr n campi, uno per ogni news prensente nellRSS.
NSMutableArray *notizie; NSMutableDictionary *notizia; NSMutableString * currentElement; NSMutableString * currentTitle; NSMutableString * currentDate; NSMutableString * currentSummary; NSMutableString * currentLink;

iPhone parsing RSS iPhone parsing RSS

MOBILE

implementare tale metodo, presentando con un UIAlerView la causa dellerrore, informazione disponibile con un codice numerico allinterno del parametro parseError, in questo modo sar anche pi semplice ricevere segnalazioni da parte degli utenti in caso di un non corretto funzionamento di questo componente. Per un elenco completo dei quasi 100 codici di errore possibili necessario consultare la documentazione associata alla classe NSXMLParser, nella sezione chiamata Parser Error Constants.
- (void)parser:(NSXMLParser *)parser parseErrorOccurred:(NSError *)parseError { NSLog(@Errore di parsing: %f, [parseError code]); UIAlertView * errorAlertView = [[UIAlertView alloc] initWithTitle:@Errore durante lanalisi message:[NSString stringWithFormat:@Errore di parsing: %f,[parseError code]] delegate:nil cancelButtonTitle:@ok otherButtonTitles:nil]; [errorAlertView show]; [errorAlertView release]; }

Perch adoperare istanze di NSMutableString invece di NSString? La flessibilit di NSMutableString ci consente di non dover allocare ed inizializzare ogni volta che troviamo un nuovo titolo, o data, o sommario o link, una nuova variabile, possiamo infatti rimuovere la stringa che tali variabili contengono semplicemente impostando il loro testo a @, questa operazione avverr quando avremo raggiunto il tag di chiusura e stiamo per passare ad analizzarne un altro. importante ricordare che le istanze di NSString sono costanti e non abbiamo controllo su quando verranno deallocate, poich durante il parsing ne verrebbero generate centinaia, non abbiamo modo di sapere se e quando queste verranno rimosse dalla memoria, situazione invece non presente con NSMutableString, classe che ci consente pieno controllo sul suo tempo di vita grazie allinvocazione di release. Prima di iniziare con metodi che si rivelano sicuramente pi interessanti importante porre lattenzione al caso in cui per qualche motivo il parser fallisca loperazione di analisi, i motivi possono essere molteplici, uno dei pi frequenti pu presentarsi quando non c sufficiente copertura sulla rete GSM, oppure non presente una la rete WIFI, situazioni quindi che rendono impossibile connettersi al server in fase di richiesta della risorsa oppure in ricezione del file RSS se la connessione si interrompe; qualunque sia il tipo di errore questo interromper il parsing, e nel caso in cui sia stato implementato il metodo parseErrorOccurred: sar responsabilit del parser provvede ad invocarlo. sempre consigliabile

Fig. 1: Il risultato finale di questo progetto

Ora bisogna ricordare quale sia la struttura pi comune di un file RSS:


<?xml version=1.0?> <rss version=2.0> <channel> <title></title> <link></link> <description></description> <language></language> <pubDate></pubDate> <lastBuildDate></lastBuildDate> <item> <title></title> <link></link> <description></description> <pubDate></pubDate> ...//possibili altri parametri </item> ...//ripetizioni di <item>...</item> </channel> </rss>

Inizializziamo allinterno del metodo parserDidStartDocument prima la stringa che conterr il nome del tag che il parser ha incontrato, e facciamo lo stesso con le stringhe che conterranno i testi trovati allinterno dei vari tag di ogni item; i tag che ci interessano sono il titolo, la data, il sommario e il link alla pagina web: title, pubDate, description e link:
i Phone / g r am Luglio 2010 pr o49 m in g

http :/ / w ww. i o p r o g r a m m o. it

74

MOBILE

iPhone parsing RSS iPhone parsing RSS

iPhone programming
do parser: foundCharacters per popolare le nostre NSMutableString e lNSDictionary solo quando siamo allinterno di tali tag, infatti parser: foundCharacters non fornisce alcun parametro che indichi in che tag siamo spetta quindi a noi tenerne traccia; adoperiamo quindi un semplice confronto tra stringhe [elementName isEqualToString:@tagname] per capire se il tag corrente di interesse o no. Nel caso in cui siamo allinterno del tag <item> provvediamo a inizializzare un NSMutableDictionary al cui interno inseriremo i contenuti di title, link, description e pubDate. Ad una prima invocazione didStartElement: fornir come elementName @title, alla seconda invocazione @title, poi @description ed infine @pubDate. Unultima nota riguarda il parametro attributeDict, questo un NSDictionary che contiene tutti gli attributi trovati per un tag, ad esempio, se il tag che abbiamo incontrato del tipo <tag name=Andrea second=Leganza age=31>Informatic Engineerer</tag> invocando su tale variabile il metodo objectForKey: adoperando come chiave il nome degli attributi presenti otterremo il valore di tali propriet.
[attributeDict objectForKey:@name]; restituisce la stringa @Andrea [attributeDict objectForKey:@second]; restiuisce la stringa @Leganza [attributeDict objectForKey:@age]; restiuisce la stringa @31

- (void)parserDidStartDocument:(NSXMLParser *) parser{ NSLog(@Parsing del documento iniziata.); currentElement = [[NSMutableString alloc] init]; currentTitle = [[NSMutableString alloc] init]; currentDate = [[NSMutableString alloc] init]; currentSummary = [[NSMutableString alloc] init]; currentLink = [[NSMutableString alloc] init]; }

Deallocheremo tali variabili solo alla fine del parsing, allinterno di parserDidEndDocument: e provvederemo infine ad aggiornare la tabella che adoperiamo per mostrare i titoli dei feed RSS.
- (void)parserDidEndDocument:(NSXMLParser *)parser{ [currentElement release]; [currentTitle release]; [currentDate release]; [currentSummary release]; [currentLink release]; NSLog(@Fine del parsing.); NSLog(@Notizie contiene %d elementi, [notizie count]); //Refresh dei contenuti della UITableView [rssTable reloadData]; }

NOTA

RIFERIMENTI WEB
Creazione dellaccount, per scaricare lSDK e consultare la documentazione previa registrazione gratuita: http://developer.apple. com/iphone/

Dopo aver inizializzato la maggior parte delle variabili necessarie per il parsing, entriamo nel vivo del codice, analizzando parser: didStartElement: namespaceURI: qualifiedName: attributes:
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementNa me namespaceURI:(NSString *)namespa ceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict{ [currentElement setString:[elementName copy]]; if ([currentElement isEqualToString:@item]) { notizia = [[NSMutableDictionary alloc] init]; } }

Adesso che siamo allinterno di un tag e possono presentarsi due situazioni: presente un ulteriore tag di apertura, come avviene per <item>, con i suoi tag figli, nel qual caso verr invocata unaltra volta didStartElement:, oppure, come avviene quando siamo in uno qualsiasi dei tag figli, verr invocato parser: foundCharacters: ad indicare che abbiamo incontrato del testo allinterno del marcatore, del tipo <tag>testo_contenuto</tag>:
- (void)parser:(NSXMLParser *)parser foundCharacters:(NSString *)string{ if ([currentElement isEqualToString:@title]) { [currentTitle appendString:string]; } else if ([currentElement isEqualToString:@link]) { [currentLink appendString:string]; } else if ([currentElement isEqualToString:@descripti on]) { [currentSummary appendString:string]; } else if ([currentElement isEqualToString:@pubDate]) { [currentDate appendString:string]; } }

Appena il parser ci notifica che ha trovato un tag di apertura, accessibile interrogando il parametro elementName, lo memorizziamo allinterno di currentElement; come avevamo mostrato prima, la struttura di un file RSS presenta molti tag diversi, description, language, pubDate, lastBuildDate e tanti altri, a noi interessano solo i tag <item>, con i tag figli <title>, <link>, <description>, <pubDate>, che contengono le singole notizie, e per tale motivo preleveremo i dati dallRSS solo quando ci troveremo allinterno di tali marcatori. Ecco svelato lutilit della variabile currentElement, questa ci servir nel meto-

Come abbiamo appena detto, currentElement ci permette di identificare il tag in cui ci troviamo,

i Ph on e p r o g r a m m ing

50 / Luglio 2010

h ttp ://www.io p r o g r a m m o .i t

75

iPhone programming
allinterno di parser:foundCharacters: popoliamo la NSMutableString in base al tag (title,link,descriptio n,pubdate) che al momento analizzato. Quando raggiungiamo il tag di chiusura di ogni singolo tag avremo uninvocazione di didEndElement:, solo nel caso in cui abbiamo raggiunto la fine della news, identifcato dal tag di chiusura </item> provvediamo a popolare con le NSMutableString generate nel metodo foundCharacters lNSMutableDictionary, inizializzato nel metodo didStartElement. Concludiamo rimuovendo lNSMutableDictionary e rimuovendo il contenuto delle NSMutableString, cos saranno pronte per essere di nuovo popolate per il prossimo item. Ricordiamo che nel caso in cui il tag svolgesse il ruolo di mero contenitore di altri tag, come avviene per <item>, non avremo alcuna chiamata a foundCharacters, quindi ipotizzando che questo tag contenga solo i 4 tag figli mostrati, avremo 4 invocazioni a foundCharacters e non 5 come alcuni potrebbero erroneamente pensare.
- (void)parser:(NSXMLParser *) parser didEndElement:(NSString *)element Name namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName{ if ([elementName isEqualToString:@item]) { [notizia setObject:[currentTitle copy] forKey:@title]; [notizia setObject:[currentLink copy] forKey:@link]; [notizia setObject:[currentSummary copy] forKey:@summary]; [notizia setObject:[currentDate copy] forKey:@date]; [notizie addObject:[notizia copy]]; [notizia release]; [currentTitle setString:@]; [currentDate setString:@]; [currentSummary setString:@]; [currentLink setString:@]; } }

iPhone parsing RSS iPhone parsing RSS

MOBILE

popoliamo quindi il corpo del metodo responsabile della visualizzazione nelle singole celle/righe, table View:cellForRowAtIndexPath:
- (UITableViewCell *)tableView:(UITableView *)table View cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *MyIdentifier = @MyIdentifier; UITableViewCell *cell = [tableView dequeueReusableCell WithIdentifier:MyIdentifier]; if (cell == nil) { cell = [[[UITableViewCell alloc] initWithStyle:UITableVie wCellStyleSubtitle reuseIdentifier:MyIdentifier] autorelease]; cell.imageView.image = [UIImage imageNamed:@news.jpg]; cell.textLabel.adjustsFontSizeToFitWidth = YES; } int notiziaIndex = [indexPath indexAtPosition: [index Path length] - 1]; cell.textLabel.text = [[notizie objectAtIndex: notizia Index] objectForKey: @title]; return cell; }

A parte le tipiche operazioni di configurazione, abbiamo invocato il metodo adjustsFontSizeToFitWidth sulla textLabel della cella allo scopo di impostare lautoresizing del testo, in modo da consentire una completa visualizzazione del titolo della news, evitando lautotroncamento del testo, operazione effettuata

LA POPOLAZIONE DELLA TABELLA


Ora che abbiamo finalmente ottenuto le informazioni che desideriamo le andremo a mostrare in una UITableView; provvediamo prima a configurare la tabella:
- (NSInteger)numberOfSectionsInTableView: (UITableView *)tableView { return 1; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { return [notizie count]; }

Fig. 2: Selezionando una cella usciremo dallapplicazione e apriremo il link con Safari

http :/ / w ww. i o p r o g r a m m o. it

76

i Phone / g r am Luglio 2010 pr o51 m in g

MOBILE

iPhone parsing RSS iPhone parsing RSS

iPhone programming
- (void)tableView:(UITableView *)tableView didSelect RowAtIndexPath:(NSIndexPath *)indexPath { int notiziaIndex = [indexPath indexAtPosition: [index Path length] - 1]; //Il link viene prelevato adoperando la chiave link sullNSDictionary. NSString *notiziaLink = [[notizie objectAtIndex: notizia Index] objectForKey: @link]; //Per sicurezza vengono sostituiti, se presenti, caratteri non compatibili con il formato accettatto per i link con opportune sequenze come ad esempio %20 per rappre sentare lo spazio notiziaLink = [notiziaLink stringByAddingPercentEscapes UsingEncoding:NSUTF8StringEncoding]; //Nel caso fossero presenti simboli di ritorno a capo vengono rimossi ...

per default da tale UILabel; otteniamo lindice della notizia per la relativa cella e lo memorizziamo allinterno della variabile notiziaIndex, andiamo quindi immediatamente a utilizzare nella riga successiva tale variabile per ottenere il relativo oggetto contenuto nellarray notizie, e preleviamo il titolo della news utilizzando la chiave title dellNSDictionary selezionato.

LA VISUALIZZAZIONE DELLE PAGINE


Ora che abbiamo presentato correttamente a video i titoli, possiamo decidere di conseguenza il modo pi adatto con cui rispondere alla selezione dellutente; avremmo potuto utilizzare una UITextView o un UIWebView ma approfittiamo di questo contesto per esporre una nuova funzionalit fornita dallSDK: lapertura di link web utilizzando Safari. In uno dei primi articoli dedicati alla programmazione su iPhone abbiamo mostrato come realizzare un mini web browser adoperando la classe UIWebView, il cui cuore lo stesso di Safari, lopen source WebKit, ma possibile anche aprire link senza dover utilizzare tale classe, a patto di accettare linconveniente di dover terminare la propria applicazione. Ogni applicativo espone al suo interno unistanza condivisa (in gergo viene chiamata un singleton) appartenente alla classe UIApplication, ottenibile invocando su tale classe il metodo sharedApplication, tale istanza fornisce una quindicina di metodi, due dei quali sono quelli che pi ci interessano in questo articolo: canOpenURL: e openURL:. Il primo metodo restituisce come risultato un booleano (YES/NO) a seconda se il link che gli inviamo associato ad almeno un software installato nel nostro telefono (esiste quindi almeno un software in grado di gestirne i contenuti), mentre il secondo provvede ad aprire tale link con il programma associato. Safari, il web browser installato di default nell iPhone/iPod Touch, impostato per default come software responsabile dellapertura dei link del tipo http://, https:// e mailto:, quindi, quando andremo a chiamare canOpenURL passando il link associato alla news, otterremo una risposta affermativa, potremo invocare openURL con la sicurezza che lutente visualizzer la pagina della notizia associata allinterno di Safari. Immediatamente dopo linvocazione di tale metodo assisteremo alla chiusura del nostro software e allapertura di Safari. Questa pratica si rivela utile nei casi in cui non si vuole impegnare tempo nella costruzione di un browser web interno, ma ha il grande limite di terminare lutilizzo del software, obbligando lutente, nel caso lo desiderasse, di dover riaprire il software e rieffettuare tutte le operazioni necessarie per mostrare nuovamente le news: quindi una soluzione da adoperare solo nelle primissime fasi di sviluppo del software, successivamente sempre opportuno realizzare un browser interno, operazione che impiega in genere poche decine di minuti.

LAUTORE Andrea Leganza Laureato in Ingegneria Informatica, certificato Adobe ACE - Adobe Flex 3 and AIR Certified Expert, EUCIP Core, Sun Certified Programmer for JAVA 6, istruttore di nuoto FIN di 2 Livello, attualmente impegnato in numerosi progetti multimediali, anche con iPhone e iPad, con alcune societ nazionali ed internazionali, contattabile su neogene@ tin.it o su www.leganza.it.

sharedApplication consente non solo di aprire link a siti web, ma anche effettuare telefonate, inviare SMS, o anche inviare email, previa sempre conferma da parte dellutente:
[[UIApplication sharedApplication] openURL:[NSURL URL WithString:@tel://123456789]] [[UIApplication sharedApplication] openURL:[NSURL URL WithString:@sms://123456789]] [[UIApplication sharedApplication] openURL:[NSURL URL WithString:@mailto:emailAdress?subject=helloSubject& body=hello my friend has been a while...]];

La necessit di richiedere conferma quando si effettua una chiamata o si invia un SMS per evitare abusi da parte di sviluppatori non proprio ortodossi, che potrebbero effettuare chiamate senza autorizzazione (potendo quindi ascoltare le conversazioni altrui), con conseguente spesa da parte dellutente, oppure inviare SMS con dati sensibili senza che lutente ne abbia controllo; ovviamente questi due metodi sono validi solo su iPhone e in questo caso se invocassimo il metodo canOpenURL su un iPod Touch otterremo risposta negativa. Lultimo metodo, quello di invio email era una delle poche soluzioni disponibili prima del rilascio dellSDK 3, ora grazie allintroduzione del componente MFMailComposeViewController sempre meno adoperato, e si possono applicare le stesse considerazioni fatte per lapertura di link http/s con Safari. anche possibile lanciare lapplicativo google maps installato nel dispositivo http://maps.google.com/<parametri>), e una pagina dellApp Store (http://phobos.apple.com/<parametri>). possibile inoltre registrare la propria applicazione come responsabile dellapertura di uno o pi formati di link ma adoperato solo in rari casi. Andrea Leganza

i Ph on e p r o g r a m m ing

52 / Luglio 2010

h ttp ://www.io p r o g r a m m o .i t

77

Questo approfondimento tematico pensato per chi vuol imparare a programmare e creare software per lApple iPhone. La prima parte del testo guida il lettore alla conoscenza degli strumenti necessari per sviluppare sulla piattaforma mobile di Cupertino. Le sezioni successive sono pensate per un apprendimento pratico basato su esempi di progetto: la creazione di un browser su misura, la gestione dellinterfaccia, la programmazione di unagenda e di una to do list, la gestione corretta di celle e tabelle, lutilizzo dellaccelerometro, la progettazione di un RSS reader e via dicendo. Una serie di esempi pratici da seguire passo passo che creando applicazioni testabili e perfettamente funzionanti - spingono il lettore a sperimentare sul campo il proprio livello di apprendimento e lo invitano a imparare divertendosi.

www.punto-informatico.it

Potrebbero piacerti anche