Programmazione iOS

71
1 Creare applicazioni iPhone e iPad: guida iOS Le guide di

Transcript of Programmazione iOS

Page 1: Programmazione iOS

  1  

Creare applicazioni iPhone e iPad: guida

iOS

Le  guide  di    

Page 2: Programmazione iOS

  2  

Introduzione ad iOS In questa guida vi insegneremo, nella maniera più chiara e al contempo esaustiva possibile, a sviluppare applicazioni su piattaforma iOS e quindi compatibili con i dispositivi di casa Apple: iPhone, iPad e iPod Touch.

iOS, come anche il diretto concorrente Android, è un sistema operativo molto giovane, che vede la luce il 9 Gennaio 2007 durante la presentazione del primo modello di iPhone. Negli anni successivi, è andato ad equipaggiare tutti gli altri dispositivi mobile di Apple come l’iPod Touch e l’iPad. L’ultima versione disponibile è iOS 6.

Dopo la presentazione dell’AppStore (il negozio virtuale nel quale acquistare le applicazioni per il proprio dispositivo) ed il rilascio dell’SDK ufficiale per la realizzazione di applicazioni, il sistema operativo ha immediatamente suscitato molto interesse sia verso singoli sviluppatori sia verso le grandi software-house. Le applicazioni per iOS hanno subito un incremento esponenziale e una diffusione grandissima, dovuta principalmente alla grande popolarità (e soprattutto alle enormi vendite) dei dispositivi Apple.

Ma perché decidere di sviluppare applicazioni per iOS? Le motivazioni principali sono le seguenti:

• Una grande diffusione. iOS equipaggia più di 250 milioni tra iPhone, iPod e iPad.

• A differenza di Android non c’è frammentazione nei dispositivi: ogni anno viene rilasciato un solo iPhone, un solo iPad ed un solo iPod Touch, quindi sarà molto più agevole produrre applicazioni ottimizzate.

• Un ambiente di sviluppo e una documentazione dell’SDK di ottimo livello.

• Un costo accessibile per l’iscrizione al programma sviluppatori (iOS Developer Program).

L’unica specifica stringente è che per poter utilizzare l’SDK ufficiale avremo necessariamente bisogno di un Mac, perché la suite di programmi per lo sviluppo gira solo su piattaforma OS X.

Nella guida verranno trattati i temi più importanti e di maggiore interesse, dedicando una prima parte alla descrizione degli strumenti di lavoro, la loro acquisizione e introdurremo i concetti di base sulla struttura di un’applicazione iOS.

Dedicheremo poi un’ampia parte della guida alla realizzazione di un’applicazione vera e propria, fornendo al lettore esempi dettagliati sull’uso degli strumenti maggiormente utilizzati dell’SDK e cercando di dare al lettore una base di conoscenze molto solida per consentire una manipolazione dei concetti introdotti in maniera totalmente autonoma.

Infine andremo ad illustrare brevemente le procedure necessarie per distribuire la propria applicazione ai betatester e come ultima operazione l’invio dell’applicazione stessa al review team di Apple per l’approvazione e la conseguente pubblicazione sull’AppStore.

Page 3: Programmazione iOS

  3  

   

Objective-C, Xcode, Interface Builder e Simulatore

In questo capitolo della guida presenteremo tutti gli strumenti di lavoro che deve avere uno sviluppatore iOS .

Objective-C

Il linguaggio di programmazione che è necessario conoscere per la realizzazione di applicazioni iOS è l’Objective-C che è un linguaggio orientato agli oggetti e di fatto rappresenta un’estensione del linguaggio C. Essendone un’estensione, l’Objective-C, mantiene una compatibilità totale con i costrutti utilizzati nel linguaggio C.

Dato che l’obiettivo di questa guida è quello di presentare al lettore le metodologie utilizzate nello sviluppo di applicazioni per iOS, daremo per scontata la conoscenza della sintassi e la struttura del linguaggio Objective-C, come anche i concetti principali della programmazione ad oggetti. Se così non fosse si consiglia al lettore di leggere la guida al linguaggio sull’Objective-C presente su HTML.it.

Xcode

Figura 1: Panoramica sull’interfaccia dell’IDE di sviluppo Xcode (clic per ingrandire)

XCode è l’IDE di sviluppo che viene offerto agli sviluppatori per la realizzazione delle proprie applicazioni. L’interfaccia del software è molto

Page 4: Programmazione iOS

  4  

chiara e intuitiva (andremo ad analizzarla in maniera più approfondita in un capitolo dedicato); inoltre l’IDE di sviluppo offre notevoli funzionalità (come l’efficientissimo completamento automatico) che permettono di ridurre sensibilmente i tempi di sviluppo.

Interface Builder

Interface Builder è un tool integrato all’interno di Xcode, che viene usato per la realizzazione delle interfacce grafiche, generando file .xib. L’utilizzo del tool è immediato: è sufficiente trascinare all’interno di un’area (che rappresenta una vista dell’applicazione) gli elementi grafici che si vogliono utilizzare (come bottoni, campi di testo, immagini) per poi passare al loro posizionamento.

I difetti principali di Interface Builder sono due: il primo è quello di dover dichiarare l’oggetto via codice e poi eseguire una vera e propria connessione tramite Interface Builder, mentre il secondo è quello di non generare automaticamente il codice relativo all’aggetto utilizzato, nascondendo quindi allo sviluppatore alcune informazioni fondamentali.

Date queste limitazioni, in questa guida non utilizzeremo il tool, ma dichiareremo, posizioneremo e personalizzeremo tutti gli oggetti via codice.

Simulatore

Ultimo strumento che andremo ad utilizzare è il simulatore che ci consente di eseguire l’applicazione realizzata direttamente sul nostro Mac senza necessariamente disporre di un dispositivo fisico come iPhone, iPod Touch o iPad. Ovviamente l’utilizzo del simulatore può andare bene per eseguire il codice presentato in questa guida, ma se il desiderio del lettore è quello di sviluppare l’applicazione concreta, che debba dunque essere pubblicata sull’AppStore, è necessario condurre i test direttamente sul dispositivo fisico.

Questo perché, il simulatore, virtualizza il comportamento del dispositivo abbastanza fedelmente, ma utilizza l’hardware offerto dal nostro Mac ed inoltre molte funzionalità come ilGPS o le Notifiche Push non sono presenti su di esso.

 

Registrazione al developer program e download di Xcode

Prima di poter iniziare a sviluppare applicazioni per iOS, dobbiamo eseguire due operazioni preliminari: iscriverci al developer program e scaricare Xcode.

Page 5: Programmazione iOS

  5  

Il riferimento principale per ogni sviluppatore è la sezione dedicata ai developer dalla quale è possibile accedere alle varie sezioni dedicate agli sviluppatori iOS, Mac e Safari.

In questo capitolo della guida andremo ad elencare le operazioni necessarie per l’iscrizione aldeveloper program che ha un costo di 79 Euro e una validità di un anno. La registrazone al developer program è necessaria per poter svolgere due operazioni: installare l’applicazione sul proprio dispositivo fisico e pubblicare le proprie applicazioni sull’AppStore.

Se il lettore non è interessato a queste funzionalità e vuole solo effettuare un po' di pratica sul simulatore, può saltare il prossimo paragrafo della guida e, se non ne possiede già uno,registrare un Apple Id (è l’account che un utente utilizza per scaricare le applicazioni dall’App Store).

Per effettuare la registrazione all’iOS developer program e sufficiente andare a questa pagina. Come possiamo vedere nella Figura 2 è possibile compiere due operazioni: registrarsi al developer program (sezione di sinistra) o effettuare degli upgrade su developer program già esistenti (sezione di destra); selezioniamo la prima voce e clicchiamo suContinue.

Figura 2: Schermata iniziale per la registrazione al developer program (clic per ingrandire)

A questo punto ci viene presentata una seconda scelta: registrarsi come sviluppatori individuali oppure come azienda; per gli scopi di questa guida scegliamo di registrarsi come sviluppatori individuali cliccando sul bottone individual in basso a sinistra.

Figura 3: Schermata per la scelta tra la registrazione individuale o come azienda

(clic per ingrandire)

Page 6: Programmazione iOS

  6  

A questo punto ci viene richiesta la compilazione di un form di registrazione ed una volta eseguita, si viene portati ad una nuova pagina nella quale è necessario compilare una sorta di questionare sulla futura attività da sviluppatore. Infine, eseguita questa procedura si passa all’accettazione dei termini di utilizzo del developer program e come ultima operazione ci viene richiesta l’immissione di un codice che ci è stato spedito per e-mail.

Figura 4: Inserimento del codice di attivazione per terminare la registrazione al developer program (clic per ingrandire)

Una volta inserito questo codice nel campo di testo, si viene portati ad una pagina di riepilogo dove è necessario confermare le informazioni inserite precedentemente ed infine alla scelta del programma di sviluppo da noi desiderato.

Page 7: Programmazione iOS

  7  

Figura 5: Selezione del programma di sviluppo che si intende realizzare (clic per ingrandire)

Ovviamente scegliamo iOS Developer Program (al costo di 79 Euro e validità di un anno) e andiamo avanti. Adesso ci viene richiesta l’accettazione della Program License Agreementnella quale vengono elencate tutti i regolamenti attinenti al programma di sviluppo scelto. A questo punto abbiamo quasi finito: è sufficiente aggiungere al carrello il programma developer scelto, effettuare il pagamento e attendere la comunicazione dell’attivazione dell’account tramite e-mail.

Figura 6: Schermata conclusiva che conferma la registrazione al developer program

(clic per ingrandire)

Terminata la procedura di registrazione, adesso possiamo eseguire il download di Xcode allaseguente pagina utilizzando le credenziali usate nella

Page 8: Programmazione iOS

  8  

registrazione del developer program (oppure con l’Apple Id usato per gli acquisti su AppStore).

 

Paradigma di programmazione MVC

Entriamo nel vivo della trattazione andando ad introdurre il Model-View-Controller design pattern (MVC) che rappresenta il design pattern più utilizzato nello sviluppo di applicazioni iOS. È necessario introdurre il design pattern MVC in quanto rappresenta uno dei concetti fondamentali della programmazione ad oggetti e permette di strutturare l’applicazione in maniera molto efficiente.

Nel design pattern MVC ogni singolo oggetto può ricoprire solo uno dei seguenti ruoli:modello, vista o controllore. Questi ruoli rappresentano una sorta di classificazione dell’oggetto che stiamo utilizzando che risultano dunque essere elementi logicamente separati ai quali però è consentita, ovviamente, una stretta comunicazione.

Figura 7: Schema illustrativo del design pattern MVC

Dunque, per dare una definizione un po’ più rigorosa, il pattern non solo stabilisce che ruolo deve avere un determinato oggetto all’interno dell’applicazione, ma anche il modo in cui gli oggetti comunicano tra di loro. Andiamo adesso a descrivere, in maniera più approfondita, i tre tipi di oggetti che il pattern definisce.

Modello

L’oggetto che ricopre il ruolo di modello è un oggetto contenente i dati specifici di un’applicazione e si occupa di definire tutti le varie procedure che effettuano la manipolazione dei dati stessi. Data la sua natura, un modello non dovrebbe avere nessun tipo di connessione diretta con un oggetto di tipo vista (che vedremo tra poco), in quanto il modello ha il compito di possedere solamente dei dati che però non devono essere legati ad un particolar tipo di visualizzazione.

Vista

Il ruolo principale di un oggetto vista è quello di presentare all’utente i dati contenuti all’interno di un modello. Dunque esiste subito una netta differenza concettuale tra modello e vista: il primo è un oggetto non concreto,

Page 9: Programmazione iOS

  9  

mentre il secondo è un oggetto concreto e con il quale l’utente può interagire andando, per esempio, a modificare i dati contenuti. Quello che offre la vista è dunque una realizzazione di un oggetto non concreto e mette a disposizione un’interfaccia per la modifica dei dati contenuti nel modello. Dato che anche un oggetto di tipo vista non dovrebbe avere un riferimento esplicito ad un oggetto di tipo modello è necessario introdurre il terzo concetto di questo patter: l’oggetto controllore.

Controllore

L’oggetto che ricopre il ruolo di controllore svolge la funzione di intermediario tra oggetti di tipo vista ed oggetti di tipo modello. Il numero di relazioni tra un singolo controllore ed oggetti di tipo modelli e vista è arbitraria: può essere una relazione uno ad uno come una relazione molti a molti. Il controllore, data la sua natura di mediatore, si occuperà di inizializzare la vista con i dati contenenti nel modello, informare la vista che i dati all’interno del modello hanno subito modifiche e rendere le modifiche effettuate dall’utente (tramite la vista) permanenti all’interno del modello.

 

Xcode: descrizione dei template offerti

In questo capitolo andremo a prendere confidenza con l’IDE di sviluppo Xcode. Per prima cosa apriamo Xcode e scegliamo l’opzione Create a new Xcode project.

Figura 8: Finestra iniziale di Xcode per la creazione di un nuovo progetto

Page 10: Programmazione iOS

  10  

A questo punto ci troviamo di fronte ad una serie di template offerti dai quali potremo partire per sviluppare la nostra applicazione.

Figura 9: Scelta dei template offerti da Xcode

Analizziamo quelli più utilizzati:

• Master-Detail Application: questo template è utilizzabile solo su iPad e offre la classica interfaccia suddivisa in due aree; a sinistra un View Controller (Master) utilizzato normalmente per la ricerca/consultazioni di dati ed un secondo View Controller (Detail) nella parte destra utilizzato per la visualizzazione dettagliata del contenuto scelto nel Master.

• Single View Application: questo template può essere usato nel caso in cui la nostra applicazione faccia uso di una singola view. Avremo infatti (oltre all’AppDelegate e alla Window, che spiegheremo tra poco) un View Controller per la gestione della view e il suo file.xib per la gestione dell’interfaccia grafica.

• Tabbed Application: questo template offre un punto di partenza per la realizzazione di un’applicazione basata sull’oggetto UITabBarController (la barra di selezione collocata nella parte bassa dello schermo in moltissime applicazioni iOS).

• Empty Application: questo template è il più semplice di tutti in quanto crea solamente l’AppDelegate della nostra applicazione e la Window. Anche se in questo primo momento può risultare di scarso interesse, questo è in realtà il template migliore da cui partire perché ci lascia completa libertà sulla realizzazione dell’applicazione. Inoltre, ci slega completamente dai file .xib e dunque dall’utilizzo di Interface Builder.

È importante precisare il seguente fatto: nel caso in cui non si voglia utilizzare Interface Builder (cosa che avviene nella realizzazione “professionale” di applicazioni) tutti questi template (tranne l’ultimo), risultano più di intralcio che di aiuto; infatti dopo averli scelti, dovremo rimuovere i riferimenti tra i view controller e i loro file .xib e successivamente eliminare fisicamente dal progetto questi file. Proprio per questo motivo, quando il lettore avrà acquisito un po’ di familiarità con gli elementi base della programmazione iOS,

Page 11: Programmazione iOS

  11  

consigliamo vivamente di partire sempre da un progetto di tipo Empty Application.

 

Creazione del primo progetto e interfaccia di Xcode

Creiamo il nostro progetto e salviamolo

A questo punto siamo pronti per creare il nostro primo progetto. Dal menu dei template selezioniamo il template Empty application e clicchiamo su next. Ci apparirà una nuova schermata nella quale ci verranno richieste alcune informazioni per la creazione del nuovo progetto:

Figura 10: Creazione di un nuovo progetto con template Empty Application

Gli unici campi che momentaneamente ci interessano sono:

• Product Name: rappresenta il nome che vogliamo dare al progetto.

• Company Identifier: è un campo che risulta necessario all’atto della pubblicazione dell’applicazione su AppStore. Per il momento possiamo inserire, come Company identifier, il testo mostrato nello screenshot.

• Device Family: indica il dispositivo sul quale l’applicazione verrà eseguita.

Page 12: Programmazione iOS

  12  

• Detto questo, clicchiamo su next, decidiamo dove collocare il nostro progetto e clicchiamo su create.

Prendiamo familiarità con l’interfaccia di Xcode

Cercheremo adesso di fare una brevissima panoramica sull’interfaccia grafica di Xcode, in modo da permettere al lettore di seguire senza problemi le lezioni presenti in questa guida.

Figura 11: Schermata principale di Xcode (clic per ingrandire)

Come prima cosa vediamo come accedere ai file del nostro progetto per poter iniziare ad modificare il codice sorgente.

Nel menu di sinistra, cliccando sulla prima icona a forma di cartella ci verrà mostrata la gerarchia dei file del nostro progetto. I file sorgenti veri e proprio (ovvero quelli dove risiede il codice Objective-C dell’applicazione) sono all’interno della cartella avente lo stesso nome del progetto (in questo caso “HelloWorld”).

Page 13: Programmazione iOS

  13  

Figura 12: Xcode: cartella dei file sorgenti

Per modificare un file dovremo semplicemente cliccare sul nome dello stesso. L’unico altro pulsante di nostro interesse è quello cerchiato in giallo in figura 11: questo ci permetterò di accedere al menu dove Xcode riporterà warning ed errori.

A questo punto nella parte di destra ci verrà mostrato il contenuto del file e potremo modificarlo.

Figura 12: Xcode: area di testo dove modificare i file sorgenti (clic per ingrandire)

Page 14: Programmazione iOS

  14  

Altra parte molto importante dell’interfaccia, sono i pulsanti per l’esecuzione e lo stop dell’applicazione mostrati in figura. Tramite il tasto con l’icona di play potremo compilare ed eseguire l’applicazione, mentre con il tasto di stop la fermeremo. Nell’interfaccia di Xcode manca però il tasto per compilare (il tasto play compila ed esegue); esistono tuttavia delle scorciatoie da tastiera sia per compilare (Build) lanciare (Run) e fermare (Stop) che sono molto comode e veloci da utilizzare:

• Build: shift+cmd+B

• Run: cmd+R

• Stop: shift+cmd+invio.

Alla destra dei pulsanti Run e Stop troviamo un’altro pulsante molto utile che ci permetterà di decidere quale schema utilizzare nella fase di compilazione e su quale dispositivo installare l’applicazione. Che cosa siano gli schemi e come si configurino esula dagli argomenti di questa guida. Facendo clic sul nome del progetto (HelloWorld) modificheremo gli schemi, mentre facendo clic sul nome del dispositivo, potremo scegliere se installare sul dispositivo fisico o sul simulatore.

Normalmente, la situazione è la seguente: il dispositivo fisico e i due simulatori (iPhone e iPad).

Figura 15: Xcode: Dispositivo e simulatore

In basso troviamo la console dove potremo stampare tutto ciò che vogliamo durante l’esecuzione dell’applicazione. L’utilizzo della console, unito ad un corretto uso dei breakpoint, è la base indispensabile per poter trovare e correggere gli errori!

Figura 16: Xcode: La console (clic per ingrandire)

Page 15: Programmazione iOS

  15  

Possiamo mostrare e nascondere la console tramite il pulsante cerchiato di rosso. Quello verde serve invece per mostrare il menu di sinistra contenente la gerarchia dei file, mentre quello azzurro ci permetterà di accedere al menu di Interface Builder.

Figura 17: Xcode: Menu, Console e Interface Builder

L’ultimo elemento di nostro interesse è il pulsante per abilitare e disabilitare i breakpoint. Una volta inseriti i breakpoint (che come in ogni IDE si inseriscono cliccando sul numero della linea di codice) potremo decidere se abilitarli o meno tramite questo pulsante.    

Avvio dell’applicazione: AppDelegate e window

Prima di analizzare il codice generato da Xcode, è necessario fare una breve introduzione su ciò che accade quando viene eseguita un’applicazione su dispositivi iOS. Al momento del lancio dell’app, viene istanziato un oggetto singleton della classe UIApplication il quale svolge il ruolo di coordinatore dell’intera applicazione smistando i vari eventi, generati dall’utente, verso gli oggetti opportuni.

Successivamente alla creazione dell’oggetto singleton della classe UIApplication viene creata un’istanza della classe AppDelegate. Il singleton è un design pattern di tipo creazionale ed il suo compito è quello di assicurare che durante l’esecuzione dell’applicazione (o di un programma in generale), una determinata classe venga istanziata una sola volta.

Passando all’analisi del codice prodotto da Xcode, possiamo vedere che nella parte sinistra dell’interfaccia, abbiamo una gerarchia di cartelle.

Page 16: Programmazione iOS

  16  

Figura 18: Struttura del progetto Empty Application

Per il momento trascuriamo tale gerarchia e concentriamo la nostra attenzione sull’unica classe Objective C presente: l’AppDelegate. Clicchiamo dunque sul file AppDelegate.h (header file) ed andiamo ad analizzarne il codice, che per comodità riportiamo di seguito: @interface AppDelegate : UIResponder <UIApplicationDelegate> @property (strong, nonatomic) UIWindow *window; @end

Come possiamo vedere la classe AppDelegate deriva dalla classe UIResponder (che rappresenta la superclasse di UIApplication) ed implementa il protocolloUIApplicationDelegate. La derivazione dalla classe UIResponder è necessaria per poter ricevere i tocchi da parte dell’utente e l’implementazione del protocolloUIApplicationDelegate è necessaria per poter implementare alcuni metodi che ci informano sugli eventi chiave durante l’esecuzione dell’applicazione come la sua terminazione, l’entrata in uno stato di inattività e molto altro.

Clicchiamo adesso sul file AppDelegate.m (il file di implementazione). Come possiamo vedere è presente una lista di metodi già implementata, ma concentriamo la nostra attenzione sul metodo application:  didFinishLaunchingWithOptions:. Quest’ultimo è il primo metodo che viene chiamato nel momento in cui l’applicazione viene lanciata ed è proprio qui che possiamo intervenire per inizializzare l’applicazione stessa e prepararla alla sua esecuzione.

La prima direttiva che troviamo in questo metodo è l’allocazione di un oggetto UIWindow che è indispensabile nella struttura di un’applicazione iOS in quanto rappresenta la root della gerarchia di viste che andremo ad aggiungere successivamente. Inoltre la window ha il compito di distribuire gli eventi alle viste presenti nella sua gerarchia. Come possiamo vedere la window viene allocata con frame (approfondiremo il concetto più avanti) pari alla dimensione dello schermo del dispositivo che stiamo utilizzando mediante la seguente linea di codice: [[UIScreen mainScreen] bounds]

La corretta inizializzazione della dimensione della window è importante per permettere ad ogniview (appartenente alla sua gerarchia) di ricevere i tocchi da parte dell’utente.

L’ultima linea di codice presente nel metodo application:  didFinishLaunchingWithOptions:rappresenta l’invocazione del

Page 17: Programmazione iOS

  17  

metodo makeKeyAndVisible sulla window che di fatto la rende visibile a schermo.

Gestire l’interfaccia: ViewController e view

Dopo avere introdotto la classe AppDelegate, andiamo a presentare altri due oggetti fondamentali per la realizzazione di un’applicazione per iOS: il View Controller e la view.

Un View Controller rappresenta l’elemento cardine di ogni applicazione iOS in quanto svolge il ruolo di gestore delle sue views (che rappresentano l’interfaccia che l’utente vede sul dispositivo) e quello di controllore, mettendo in relazione le varie views con il modello dei dati. La classe UIViewController possiede, come attributo, una view, ma nulla vieta di aggiungerne altre e consentire così al ViewController creato di gestire un’intera gerarchia di views.

La classe UIView definisce un’area rettangolare sullo schermo del dispositivo all’interno della quale posizionare gli elementi grafici della pagina. Durante l’esecuzione dell’applicazione il compito di una view è quello di mostrare a schermo i contenuti (inseriti su di essa) e gestire l’interazione dell’utente con tali contenuti (ad esempio tocchi su pulsanti e gestures).

La peculiarità della classe UIView è quella di permettere l’incapsulamento di altre viewscreando una struttura padre-figlio: il padre viene detto superview mentre i suoi figli subviews. Per la creazione di una gerarchie di views è sufficiente fare riferimento a due metodi (vedremo più avanti, con un esempio, come utilizzarli):

• metodo addSubView: aggiunge una view figlia alla superview;

• metodo removeFromSuperview: rimuove una subView dalla superView.

Gli oggetti grafici che ci vengono messi a disposizione dall’SDK eriditano tutti dalla classeUIView e quindi su di essi potremo invocare i precedenti metodi.

A questo punto, vediamo la procedura necessaria per aggiungere un View Controller al nostro progetto HelloWorld. Torniamo su Xcode e dal menu in alto, selezioniamo File, poi andiamo sulla voce New ed infine clicchiamo su New File; si aprirà una nuova finestra come quella mostrata nella seguente figura:

Figura 19: Creazione di un nuovo View Controller selezionando la tipologia della classe

(clic per ingrandire)

Page 18: Programmazione iOS

  18  

Come possiamo vedere è possibile scegliere diverse tipologia di classe (che in codice si traduce in ereditarietà) tra cui UIViewController che ci interessa adesso; selezioniamo tale voce e clicchiamo su next. Si aprirà la seguente finestra:

Figura 20: Scelta del nome e delle proprietà del nuovo View Controller (clic per ingrandire)

Page 19: Programmazione iOS

  19  

Inseriamo dunque il nome del nuovo View Controller, la classe da cui ereditare (lasciamoUIViewController come ci propone Xcode) ed infine assicuriamoci che non siano presenti le spunte su i due checkbox. Cliccando su next per terminare la procedura di creazione la finestra scomparirà e il nuovo View Controller apparirà nella lista dei file di progetto in alto a sinistra.

Il file FirstViewController.h, contiene esclusivamente il prototipo della classe dato che tutti gli elementi a cui potremo accedere, compresa la view del View Controller, sono ottenuti grazie all’ereditarietà dalla classe UIViewController. Cliccando sul file FirstViewController.m troviamo l’implementazione della classe appena creata. Analizziamo i metodi di maggior interesse:

• initWithNibName:bundle:: rappresenta il metodo di init di default dei View Controller;

• shouldAutorotateToInterfaceOrientation:: è il metodo che viene richiamato direttamente dal dispositivo quando viene rilevato un cambiamento di rotazione. In questo metodo potremo decidere se supportare o meno tutte le orientazioni del dispositivo.

Nella prossima lezione, fatte queste premesse, potremmo passare a provare una nostra applicazione di prova sull’emulatore.

Creiamo la prima app per iPhone e iPad

A questo punto siamo pronti per dare forma all’Hello World ed eseguire per la prima volta un’applicazione sul simulatore. Le operazioni che andremo ad effettuare saranno sostanzialmente due: inserire una label all’interno del FirstViewController ed infine mostrarla a schermo.

Aggiungiamo la label Hello World

Come abbiamo detto nella lezione precedente tutti i View Controllers posseggono un init di default che andremo ad utilizzare in questo primo progetto. Come possiamo notare il corpo del metodo contiene già qualche linea di codice che riportiamo per comodità di seguito: self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];      if (self) {  }      return self;

Nella prima riga di codice viene invocato lo stesso metodo di init della superclasse (ricordiamo che la nostra classe FirstViewController deriva da UIViewController) e dopo viene eseguito un if sul valore del self per assicurarci che la chiamata al super sia stata fatta correttamente. A questo punto possiamo inserire, all’interno dell’if, la nostra label Hello World nel seguente modo:

Page 20: Programmazione iOS

  20  

UILabel *myLabel = [[UILabel alloc]initWithFrame:CGRectMake(100, 100, 300, 50)];      myLabel.text = @"Hello World";      myLabel.textColor = [UIColor blackColor];      [self.view addSubview:myLabel];      [myLabel release];

Nel codice proposto allochiamo ed inizializziamo la label assegnandole un frame che rappresenta la posizione e la dimensione del nostro oggetto. I parametri in ingresso alla funzione CGRectMake (che definisce un rect, ovvero un’area precisa dello schermo) sono rispettivamente: l’offset x rispetto all’origine, l’offset y rispetto all’origine, larghezza ed altezza (nella prossima lezione parleremo più approfonditamente del sistema di riferimento). Per il momento ignoriamo l’invocazione del metodo release sull’oggetto UILabel dato che l’argomento verrà trattato successivamente.

Assegnamo poi (rigo 2) il testo alla label, definiamo il colore del testo ed infine aggiungiamo lalabel alla gerarchia della view presente nel View Controller invocando il metodo addSubView(senza l’invocazione di tale metodo la label non sarà visibile a schermo).

Mostriamo a schermo il View Controller

L’ultima operazione che andiamo ad eseguire è quella di mostrare a schermo il nostroFirstViewController. Per fare ciò, spostiamoci nel file AppDelegate.m ed importiamo l’header file della nostra classe nel seguente modo: #import "FirstViewController.h"

Posizioniamoci ora nel metodo application:  didFinishLaunchingWithOptions: e prima della chiamata al metodo makeKeyAndVisible inseriamo il seguente codice:

FirstViewController * myViewController = [[FirstViewController alloc]initWithNibName:nil bundle:nil];      self.window.rootViewController = myViewController;      [myViewController release];

Come prima cosa allochiamo il nostro View Controller passando come argomenti al metodo diinit di default il valore nil dato che non richiediamo il caricamento di un’interfaccia grafica realizzata con Interface Builder. Infine impostiamo il View Controller appena creato comerootViewController, ovvero come radice della gerarchia di View Controller associati allaWindow (anche in questo esempio ignoriamo l’invocazione del metodo release sul View Controller).

A questo punto siamo pronti per compilare il progetto; clicchiamo sul tasto di Play in Xcode e dopo l’avvio del simulatore quest’ultimo mostrerà la seguente schermata:

Page 21: Programmazione iOS

  21  

Figura 21: Hello World: schermata del nostro primo progetto

Facile no?

Posizionare gli oggetti in un’app per iOS

Una cosa molto importante da capire nello sviluppo di applicazioni iOS è la logica che sta dietro al posizionamento degli oggetti all’interno delle views. Non utilizzando Interface Builder, dovremo posizionare gli oggetti indicandone la posizione tramite codice. Riprendiamo in mano il progetto Hello  World, cancelliamo la label e riportiamo il metodo di init, della classeFirstViewController nel seguente stato:

self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];      if (self) {  }      return self;

Il sistema di riferimento

Il sistema di riferimento delle views è un sistema cartesiano ribaltato rispetto all’asse y: la sua origine è dunque in alto a sinistra e la coordinata x cresce andando verso destra, mentre la coordinata y cresce andando verso il basso.

Figura 22: Sistema di riferimento generale delle UIView

Page 22: Programmazione iOS

  22  

Quindi se posizioniamo una nuova view con origine in (0,0) questa apparirà in alto a sinistra e si estenderà verso destra e verso il basso, come mostrato nel codice seguente da inserire nel file FirstViewController.m nel metodo di initWithNibName:  bundle::

UIView *secondView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];      secondView.backgroundColor = [UIColor redColor];      [self.view addSubview:secondView];      [secondView release];

Figura 23: Posizionamento di una view nell’origine (0,0)

Page 23: Programmazione iOS

  23  

Centriamo adesso la nuova vista: per fare questo dovremo scrivere qualche linea di codice per poter calcolare la posizione corretta. Può sembrare una perdita di tempo, ma vedrete che avere il codice scritto (e non nascosto da Interface Builder) vi permetterà di localizzare velocemente gli errori commessi. Andiamo quindi a sostituire il codice precedentemente inserito con il seguente CGFloat width = 100;  CGFloat height = 100;  CGFloat x = (self.view.frame.size.width - width) / 2;  CGFloat y = (self.view.frame.size.height - height) / 2;  <br>  UIView *secondView = [[UIView alloc] initWithFrame:CGRectMake(x,y,width,height)];  secondView.backgroundColor = [UIColor redColor];  [self.view addSubview:secondView];  [secondView release];

Figura 24: Posizionamento di una view al centro dello schermo

Page 24: Programmazione iOS

  24  

Aggiungiamo adesso una terza view, “attaccandola” però all’oggetto secondView; per fare questo dovremo creare la terza view e richiamare il metodo addSubview sull’oggetto secondViewpassando,come argomento, la nuova vista.

È importante tenere presente che il sistema di riferimento utilizzato nel posizionamento degli elementi è quello dell’oggetto su cui viene richiamato il metodo addSubview; quindi per posizionare la terza vista nell’angolo in basso a destra della seconda dovremo fare in questo modo (inseriamo il codice dopo la dichiarazione dell’oggetto secondView sempre nel metodoinitWithNibName:  bundle: nel file FirstViewController.m):

CGFloat width2 = 50;  CGFloat height2 = 50;  CGFloat x2 = secondView.frame.size.width - width2;  CGFloat y2 = secondView.frame.size.height - height2;      UIView *thirdView = [[UIView alloc] initWithFrame:CGRectMake(x2,y2,width2,height2)];  thirdView.backgroundColor = [UIColor yellowColor];  [secondView addSubview:thirdView];  [thirdView release];

Figura 24: Posizionamento relativo di un oggetto UIView rispetto ad un altro oggetto UIView

Page 25: Programmazione iOS

  25  

Questi semplici esempi dovrebbero rendere chiaro il problema principale a cui si va incontro quando si posizionano gli oggetti via codice: ovvero quello di considerare il sistema di riferimento sbagliato nel calcolare l’origine degli elementi grafici.

Nel caso in cui doveste commettere un errore di questo tipo, andrete incontro a due risultati: o vedete l’oggetto in una posizione completamente sbagliata o non vederlo proprio perché magari è posizionato fuori dall’area visibile dello schermo.

Gestiamo la rotazione del dispositivo in un app’per iOS

Dopo aver visto come posizionare gli elementi grafici, vediamo come gestire la rotazione del dispositivo. Facciamo un’importante premessa: quando il dispositivo ruota, il sistema di riferimento rimane comunque posizionato con l’origine nell’angolo in alto a sinistra.

Page 26: Programmazione iOS

  26  

Figura 25: Rotazione del dispositivo: sistema di riferimento sempre in alto a sinistra

La rotazione non è obbligatoria e dovremo abilitarla solo nel caso in cui entrambe le orientazioni risultino di aiuto per mostrare al meglio i contenuti dell’applicazione. La rotazione deve essere abilitata per ogni view controller nel quale si renda necessaria: non è un’impostazione a livello di applicazione. Quindi potrete avere un’applicazione dove magari ruota solo un View Controller.

Riprendiamo in mano il progetto visto nella lezione precedente e portiamoci nel fileFirstViewController.m. Per abilitare la rotazione dovremo modificare il valore di ritorno del metodo (presente in ogni view controller)

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return (interfaceOrientation == UIInterfaceOrientationPortrait); }

che di default supporta solo l’orientazione Portrait (ovvero quella classica: dispositivo verticale e pulsante home in basso). Per abilitare le altre orientazioni potete fare in due modi: far ritornare al metodo semplicemente il valore YES che significa abilitare tutte e quattro le orientazioni, oppure abilitare singolarmente quelle desiderate.

Poiché normalmente le applicazioni iPhone non dovrebbero supportare la modalità Portrait Upside Down (dispositivo verticale, ma il pulsante di home in alto), utilizzeremo il secondo metodo. Modifichiamo quindi il valore di ritorno nel seguente modo: return (interfaceOrientation == UIInterfaceOrientationPortrait) || (interfaceOrientation == UIInterfaceOrientationLandscapeLeft) || (interfaceOrientation == UIInterfaceOrientationLandscapeRight);

Page 27: Programmazione iOS

  27  

Compilando l’applicazione noterete che adesso l’interfaccia ruota nelle tre orientazioni che abbiamo indicato, ma la view rossa che abbiamo inserito (l’oggetto secondview presente nel fileFirstViewController.m) risulta centrata solo in modalità Portrait. In particolare, in modalitàLandscape (dispositivo orizzontale), l’origine della view risulta essere nello stesso punto di prima, solo che essendo cambiata l’orientazione, la sua posizione non è più centrata.

Figura 26: Rotazione del dispositivo Landscape left: views non centrate

Il motivo è che non abbiamo detto all’oggetto secondView come comportarsi durante la rotazione e come modificare la sua distanza dai bordi dello schermo. Per fare questo, dobbiamo utilizzare la property autoresizingMask che ci permetterà di definire sei diverse regole per la view:

• UIViewAutoresizingFlexibleBottomMargin: se inserito fa si che la distanza della view dal bordo inferiore sia flessibile.

• UIViewAutoresizingFlexibleTopMargin: se inserito fa si che la distanza della view dal bordo superiore sia flessibile.

• UIViewAutoresizingFlexibleRightMargin: se inserito fa si che la distanza della view dal bordo destro sia flessibile.

• UIViewAutoresizingFlexibleLeftMargin: se inserito fa si che la distanza della view dal bordo sinistro sia flessibile.

• UIViewAutoresizingFlexibleWidth: se inserito fa si che la larghezza della view sia flessibile.

• UIViewAutoresizingFlexibleHeight: se inserito fa si che l’altezza della view sia flessibile.

Page 28: Programmazione iOS

  28  

Cosa significa flessibile? Flessibile significa che, al contrario di prima, la distanza dai bordi (o le dimensioni) della view verrà modificata in relazione alla nuova orientazione in modo da mantenere all’interno dello schermo la stessa posizione relativa.

Dobbiamo dunque capire di cosa abbiamo bisogno affinché la nostra view risulti ancora centrata quando il dispositivo ruota. Possiamo notare che la distanza dal bordo superiore e da quello sinistro è rimasta la stessa: abbiamo allora bisogno di inserire i due valoriUIViewAutoresizingFlexibleTopMargin e UIViewAutoresizingFlexibleLeftMargin.

Riportiamoci nel metodo initWithNibName:  bundle: del file FirstViewController.m e scriviamo la seguente linea di codice prima dell’addSubView della secondView:

secondView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin;

Quello che otterremo è il seguente risultato:

Figura 27: Rotazione del dispositivo con UIViewAutoresizingFlexibleTopMargin e UIViewAutoresizingFlexibleLeftMargin

C’è evidentemente qualcosa che non va! La view non è centrata e adesso ha il problema inverso:

• la distanza dal bordo inferiore e da quello destro è rimasta la stessa rispetto alla modalitàPortrait: abbiamo allora bisogno di inserire i due valoriUIViewAutoresizingFlexibleBottomMargin e UIViewAutoresizingFlexibleRightMargin

Quello che ci siamo scordati di considerare è che la view deve modificare anche la distanza dal bordo destro e da quello inferiore, e non solo da quello superiore e sinistro. Abbiamo quindi bisogno di inserire tutte e quattro queste

Page 29: Programmazione iOS

  29  

“regole” andando a modificare le linee di codice precedentemente inserite come segue: secondView.autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleRightMargin;

Ed ecco che finalmente la nostra view risulterà centrata.

Figura 28: Rotazione del dispositivo: view centrata dopo aver applicato i giusti Autoresizing

Dopo questa breve spiegazione, il lettore dovrebbe riuscire a capire anche come mai la viewgialla posizionata su quella rossa è rimasta al suo posto nell’angolo in basso a destra: il motivo è che non avendo dichiarato nessuna regola di autoresizing, questa manterrà fisse le distanza dai quattro bordi senza spostarsi. E poiché la view rossa (a differenza di quello che avviene per lo schermo) non cambia dimensione, non abbiamo bisogno di dichiarare nessuna regola.

Se invece inseriamo anche le regole di UIViewAutoresizingFlexibleWidth eUIViewAutoresizingFlexibleHeight alla view rossa (secondView) andremo a modificarne la dimensione e questo è quello che otterremo:

Figura 29: Rotazione del dispositivo: risultato dopo l’associazione degli Autoresizing per le dimensioni solo alla secondView

Page 30: Programmazione iOS

  30  

Il problema non è che la view gialla (thirdView) sia rimasta ferma (perché questo è quello che deve fare per rimanere nell’angolo in basso a sinistra), ma che non abbia adattato le dimensioni in relazione alla view rossa. Inseriamo quindi la seguente linea sempre nel metodoinitWithNibName:  bundle: del file FirstViewController.m prima dell’addSubview della thirdView:

thirdView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth;

Quello che otterrete è questo:

Figura 30: Rotazione del dispositivo: esempio finale sul corretto uso degli Autoresizing

la view gialla ha mantenuto la sua posizione all’interno dei quella rossa, modificando correttamente le proprie dimensioni.

Page 31: Programmazione iOS

  31  

Gestione della memoria e ARC

In precedenza abbiamo trascurato l’invocazione del metodo release sugli oggetti, necessaria per una corretta gestione della memoria i cui concetti principali verranno introdotti di seguito. I dispositivi iOS non posseggono un Garbage Colllector il quale gestirebbe automaticamente la memoria dell’intero dispositivo, ma bensì delega agli sviluppatori una gestione della memoria “manuale” nello sviluppo delle loro applicazioni.

Fulcro della gestione della memoria è una proprietà che ogni oggetto possiede: ilretainCount. Quest’ultimo indica il numero di riferimenti associati all’oggetto e quando ilretainCount di qualsiasi oggetto arriva a 0 quest’ultimo viene deallocato.

Questa operazione viene effettuata da un oggetto chiamato AutoreleasePool che è istanziato non appena l’applicazione viene eseguita e si occupa di monitorare i retainCount degli oggetti all’interno dell’applicazione per deallocare quelli con valore 0.

Ma in che modo cambia il valore del retainCount? Quando un qualsiasi oggetto viene allocato ed inizializzato con i classici metodi alloc e init, il suo retainCount ha valore 1. Per modificare in maniera esplicita il retainCount di un oggetto allocato si possono usare due metodi:

• retain: incrementa il retainCount di 1.

• release: decrementa il retainCount di 1

Ritornando alla nostra “applicazione” di prova Hello World, e facendo un rapido conto delretainCount della label, abbiamo la sua inizializzazione (retainCount = 1) e poco dopo una chiamata al metodo release il quale, decrementando di uno il retainCount, verrebbe riportato a 0 e la label verrebbe deallocata.

Ma perché l’applicazione funziona correttamente anche se la label ha retainCount zero? Semplicemente perchè il retainCount non è 0; infatti, ricordando la definizione del retainCount(ossia che rappresenta il numero di riferimenti all’oggetto), l’invocazione del metodoaddSubview sulla view del View Controller incrementa di 1 il retainCount della label (quindi dopo la chiamata a quel metodo il retainCount vale 2) in quanto aggiunge quest’ultima alla propria gerarchia ed ha bisogno di un riferimento esplicito alla label stessa. Infine con la chiamata al metodo release riportiamo il retainCount ad 1.

Ma quando verrà deallocata la label? Ciò avverrà quando il firstViewController verrà deallocato e dunque, non essendo più necessario il riferimento alla label, il suo retainCountverrà decrementato di 1 (assumendo dunque valore 0) e, finalmente, anche la label verrà deallocata.

Come abbiamo detto prima, dopo l’invocazione del metodo addSubView il retainCount della labelviene portato a 2; risulta dunque indispensabile la chiamata esplicita al metodo release per riportare il retainCount dela label a 1 per poter essere deallocata nello scenario descritto poco sopra.

Page 32: Programmazione iOS

  32  

Errata gestione della memoria: leak e crash

Una errata gestione della memoria può portare ad uno dei seguenti problemi: un leak di memoria o un crash dell’applicazione.

Il primo problema è legato ad un retainCount di un oggetto troppo elevato. Tale oggetto non verrà mai deallocato (in quanto possiede un retainCount maggiore di 1) causando quindi uno “spreco” di memoria.

Il secondo problema (il crash dell’applicazione), è causato principalmente da un retainCounttroppo basso su un oggetto che causa una deallocazione prematura dell’oggetto stesso. Di per se, la deallocazione prematura di un oggetto non causa il crash dell’applicazione, ma se per esempio proviamo ad invocare un metodo su tale oggetto l’applicazione crasherà dato che si sta provando ad accedere ad un’area di memoria vuota.

Analizzando i due scenari il primo è sicuramente meno critico, ma bisogna fare attenzione: se il numero di leak è elevato si rischia che l’applicazione uccupi una quantità di memoria eccessiva e, superata una certa soglia, il sistema operativo del dispositivo terminerà esso stesso l’applicazione.

ARC (Automatic Reference Counting)

Con il rilascio di Xcode 4.2 è stato introddo ARC, una funzionalità molto interessante che semplifica la vita agli sviluppatori. Utilizzando ARC lo sviluppatore non è più costretto ad invocare esplicitamente i metodi retain e release per mantenere un valore coerente delretainCount dell’oggetto, dato che queste direttive vengono inserite, a compile-time da ARCstesso. ARC dunque non è un garbage collector in quanto non lavora a run-time.

Nel tutorial pratico che presenteremo in questa guida utilizzeremo proprio ARC e dunque il lettore non troverà più riferimenti espliciti a metodi di retain e release.

Applicazione di esempio con iOS: introduzione

Nelle varie lezioni di questa sezione della guida andremo a mostrare al lettore la realizzazione di un’applicazione demo, affinché possa prendere familiarità con gli oggetti più utilizzati messi a disposizione dall’SDK di sviluppo.

L’applicazione è articolata nelle tre seguenti schermate:

• Pagina di inserimento: in questa pagina verrà mostrato un modulo di inserimento dati. Si suppone di voler inserire i dati (nome, origine, provenienza) relativi ad un frutto. La schermata di riferimento è la seguente:

Figura 31: Schermata di inserimento dati per l’applicazione demo

Page 33: Programmazione iOS

  33  

• Pagina di visualizzazione dati: in questa pagina, per mezzo di una tabella, verranno mostrati all’utente i dati precedentemente inseriti. La schermata di riferimento è la seguente:

Figura 32: Schermata di visualizzazione dati per l’applicazione demo

Page 34: Programmazione iOS

  34  

• Pagina di visualizzazione dettaglio frutto: in questa pagina, verrà mostrato il dettaglio dei dati di un frutto dopo che l’utente ha toccato una cella della tabella. La schermata di riferimento è la seguente: Figura 33: Schermata di visualizzazione del dettaglio dei dati di un frutto per

l’applicazione demo

Page 35: Programmazione iOS

  35  

L’obiettivo di questa parte della guida è quello di illustrare al lettore l’utilizzo degli oggetti che andremo ad utilizzare fornendogli così una base solida che consentirà al lettore di personalizzare ed ampliare il progetto di prova a piacimento.

Creiamo la barra delle tab

 

Iniziamo la creazione del nostro progetto di prova seguendo le istruzioni elencate nei capitoli precedenti. La prima cosa che è necessario fare è l’inserimento della TabBar posizionata nella parte bassa dello schermo come possiamo vedere, per esempio, nell’applicazioneOrologio preinstallata sui dispositivi Apple:

Figura 34: Esempio di utilizzo della TabBar nell’applicazione Orologio

Page 36: Programmazione iOS

  36  

Quello di cui abbiamo bisogno è un’istanza di UITabBarController che è un view controllerspecializzato nella gestione di altri view controller. Un oggetto di tipo UITabBarController, come possiamo vedere dalla figura precedente, possiede un insieme di tab le quali sono associate ad altrettanti view controller creati dallo sviluppatore.

Quando l’utente tocca una tab viene mostrato a schermo la view del view controller associata al tab selezionato, andando a sostituire qualsiasi altro contenuto mostrato precedentemente.

Le operazione che dobbiamo compiere per realizzare questa prima parte del progetto sono sostanzialmente due: creare i view controller di cui avremo bisogno ed associarli ad un oggetto UITabBarController.

Seguendo la procedura esposta nei capitoli precedenti per la creazione di un nuovo file da aggiungere al progetto creiamo due view controller con i seguenti nomi: InsertViewControllere ListViewController. Inoltre nel metodo initWithNibName:  bundle:, inseriamo una colorazione diversa per la view dei due View Controller con il seguente codice nel fileInsertViewController.m:

self.view.backgroundColor = [UIColor redColor];

e nel file ListViewController.m self.view.backgroundColor = [UIColor yellowColor];

A questo punto non ci resta che istanziare un oggetto di tipo UITabBarController ed associare alle sue tab i view controllers che abbiamo creato precedentemente. Come abbiamo detto nelle lezioni precedenti, l’AppDelegate è la classe in cui viene inizializzata l’applicazione

Page 37: Programmazione iOS

  37  

ed è per questo che dovremo inserire l’oggetto UItabBarController proprio in tale classe dichiarandolo come dato membro nel file AppDelegate.h: @interface AppDelegate : UIResponder &ltUIApplicationDelegate>{ UITabBarController *_tabBarController; }

Adesso spostiamo nel file AppDelegate.m ed aggiungiamo gli header file necessari: #import "InsertViewController.h" #import "ListViewController.h"

Infine posizioniamoci nel metodo application:  didFinishLaunchingWithOptions: e prima dell’invocazione al metodo makeKeyAndVisible della window inseriamo il seguente codice:

NSMutableArray *controllers = [[NSMutableArray alloc] initWithCapacity:2]; _tabBarController = [[UITabBarController alloc]init]; ListViewController *listViewController = [[ListViewController alloc]initWithNibName:nil bundle:nil]; listViewController.title = @"Lista"; listViewController.tabBarItem.image = [UIImage imageNamed:@"list.jpg"]; InsertViewController *insertViewController = [[InsertViewController alloc]initWithNibName:nil bundle:nil]; insertViewController.title = @"Inserimento"; insertViewController.tabBarItem.image = [UIImage imageNamed:@"plus.jpg"]; insertViewController.delegate = listViewController; [controllers addObject:insertViewController]; [controllers addObject:listViewController]; _tabBarController.viewControllers = controllers;

Facciamo una breve analisi del codice inserito. Come prima cosa viene dichiarato l’arraycontrollers con dimensione 2 che andrà a contenere i view controllers che saranno associati all’oggetto UITabBarController che viene allocato ed inizializzato nella linea successiva. Passiamo poi alla dichiarazione dei due view controllers (nella stessa maniera vista precedentemente nel progetto Hello World) che saranno associati ai tab del’oggettotabBarController. Come possiamo vedere, ad entrambi i view Controllers, settiamo laproperty  title il cui testo verrà mostrato nel bottone della TabBar ed aggiungiamo i view controllers all’array controllers.

Infine associamo l’array controllers (che ricordiamo conterrà un oggetto di tipoInsertViewController ed uno ListViewController) all’array viewControllers dell’oggettotabBarController. Infine invochiamo il metodo addSubView sulla window e passiamo come parametro la view del tabBarController per mostrarla a schermo.

Compilando l’applicazione il risultato che otterremo è il seguente:

Figura 35: Schermate dell’applicazione dopo l’inserimento della tabBar (clic per ingrandire)

Page 38: Programmazione iOS

  38  

Nella prossima lezione andremo ad aggiungere un’icona per ogni tab del tabBarController.

Creiamo le icone per le tab

Prima di aggiungere le icone ai tab del tabBarController è necessario fare una breve premessa sulle specifiche dei file grafici da utilizzare per immagini o icone.

Dopo l’introduzione dell’iPhone 4 e dell’iPad di terza generazione è stato introdotto uno schermo con risoluzione doppia rispetto ai dispositivi precedenti. Per questo motivo avremo bisogno di avere ogni elemento grafico in due risoluzioni diverse: una per i dispositivi con supporto al retina display e una per i dispositivi che non lo supportano.

Ma come facciamo a sapere quando caricare un elemento grafico ad alta risoluzione? Non ci dovremo preoccupare di questo, dato che iOS gestisce in maniera automatica le differenti risoluzioni per ogni elemento grafico. In particolare: se un’applicazione è in esecuzione su un dispositivo con retina display mostrerà a schermo immagini ed icone ad alta risoluzione, viceversa caricherà immagine e icone in risoluzione standard.

Lo sviluppatore è tenuto a seguire una sola regola per consentire al sistema operativo questo automatismo: aggiungere il suffisso @2x per i file grafici in alta risoluzione prima dell’estensione del file stesso. Per fare un esempio se abbiamo un’immagine chiamataprova.png di dimensione 20×20 pixel, dovremo avere anche un’immagine [email protected] di dimensione 40×40 pixel.

Page 39: Programmazione iOS

  39  

Detto questo vediamo la procedura da eseguire per importare all’interno del nostro progetto i file grafici che rappresenteranno le icone dei tab della TabBar. Come prima cosa creiamo una nuova cartella chiamata Graphics che andrà a contenere tutte le immagini che utilizzeremo in questo progetto. Per crearla è sufficiente cliccare su File (nel menu in alto di Xcode), poi suNew ed infine su New Group. Vedrete apparire, nella gerarchia dei file di progetto la nuova cartella che andremo a rinominare semplicemente con un doppio click.

A questo punto clicchiamo su File e poi su Add File To “NomeDelProgetto”.

Figura 36: Schermata per importare elementi grafici all’interno di un progetto Xcode

Selezioniamo dunque i quattro file necessari (relativi alle due icone in alta e bassa risoluzione) e spuntiamo il checkbox Copy items into destination (copierà i file grafici all’interno della cartella del nostro progetto) come nella figura precedente. Infine clicchiamo su Add.

Adesso siamo pronti per scrivere le linee di codice necessarie per mostrare tali immagini all’interno dei tab. Torniamo dunque nel file AppDelegate.m nel metodo application:  didFinishLaunchingWithOptions: e dopo la linea di codice insertViewController.title  =  @"Inserimento" inseriamo:

insertViewController.tabBarItem.image = [UIImage imageNamed:@"plus.jpg"];

Stessa cosa per l’altro View Controller; dopo la linea di codice listViewController.title  =  @"Lista" inseriamo:

listViewController.tabBarItem.image = [UIImage imageNamed:@"list.jpg"];

Page 40: Programmazione iOS

  40  

Con quella linea di codice semplicemente creiamo una UIImage con il nome dell’icona appropriata e l’associamo alla property tabBarItem.image.

A questo punto effettuando un Run del progetto otterremo la schermata seguente:

Figura XX: Schermata dell’applicazione con tab contenente un’icona

Aggiungiamo le labels al modulo di inserimento

Iniziamo adesso a personalizzare il nostro InsertViewController aggiungendo le etichette (olabels) che andranno a descrivere i vari campi che formeranno il modulo di inserimento. self.view.backgroundColor = [UIColor scrollViewTexturedBackgroundColor];  UILabel * name = [[UILabel alloc]initWithFrame:CGRectMake(10, 10, 310, 40)];  name.text = @"Nome";  name.backgroundColor = [UIColor clearColor];  name.textColor = [UIColor whiteColor];  name.font = [UIFont boldSystemFontOfSize:20.0];  [self.view addSubview:name];  UILabel * origin = [[UILabel alloc]initWithFrame:CGRectMake(10, 85, 310, 40)];  origin.text = @"Orgine";  origin.backgroundColor = [UIColor clearColor];  origin.textColor = [UIColor whiteColor];  origin.font = [UIFont boldSystemFontOfSize:20.0];  [self.view addSubview:origin];  

Page 41: Programmazione iOS

  41  

UILabel * description = [[UILabel alloc]initWithFrame:CGRectMake(10, 160, 310, 40)];  description.text = @"Descrizione";  description.backgroundColor = [UIColor clearColor];  description.textColor = [UIColor whiteColor];  description.font = [UIFont boldSystemFontOfSize:20.0];  [self.view addSubview:description];

Come prima cosa rendiamo più gradevole lo sfondo della view del view controller associando alla property backgroundColor la trama scrollViewTexturedBackgroundColor (quella di default che troviamo in molte applicazione native Apple).

Le dichiarazioni delle labels sono le medesime di quelle viste nell’Hello World con la sola aggiunta del metodo boldSystemFontOfSize che ci permette di definire la grandezza del testo della label associandole anche lo stile bold.

Inseriamo i campi di testo per l’immissione dei dati

Continuiamo la composizione del modulo di inserimento dati inserendo altri tre elementi che consentiranno all’utente l’inserimento del testo per mezzo della tastiera virtuale del dispositivo. Questi tre elementi rappresentano due oggetti ben distinti: due appartenenti alla classeUITextField ed uno appartenente alla classe UITextView.

La differenza tra i due oggetti è molto semplice da capire: il primo oggetto è utilizzato per l’inserimento di un testo di lunghezza contenuta su una sola linea, mentre il secondo oggetto consente l’inserimento di testo di lunghezza superiore e su più linee.

Passiamo alla parte pratica. A differenza delle labels create nella lezione precedente, avremo bisogno di dichiarare gli oggetti per l’inserimento del testo come attributi di classe, in quanto avremo bisogno di accedervi anche in un secondo momento. Spostiamoci dunque nel fileInsertViewController.h ed inseriamo le seguenti linee di codice: UITextField *_nameTextField; UITextField *_originTextField; UITextView *_descriptionTextView;

Spostiamoci adesso nel file InsertViewController.m e nel metodo initWithNibName:  bundle:, dopo la dichiarazione delle labels, inseriamo il seguente codice: _nameTextField = [[UITextField alloc]initWithFrame:CGRectMake(10, 45, 300, 30)]; _nameTextField.placeholder = @"Inserisci nome"; _nameTextField.borderStyle = UITextBorderStyleRoundedRect; _nameTextField.backgroundColor = [UIColor whiteColor]; _nameTextField.clearButtonMode = UITextFieldViewModeWhileEditing; [self.view addSubview:_nameTextField]; _originTextField = [[UITextField alloc]initWithFrame:CGRectMake(10, 120, 300, 30)]; _originTextField.placeholder = @"Inserisci origine"; _originTextField.borderStyle = UITextBorderStyleRoundedRect;

Page 42: Programmazione iOS

  42  

_originTextField.backgroundColor = [UIColor whiteColor]; _originTextField.clearButtonMode = UITextFieldViewModeWhileEditing; [self.view addSubview:_originTextField]; _descriptionTextView = [[UITextView alloc]initWithFrame:CGRectMake(10, 195, 300, 70)]; _descriptionTextView.backgroundColor = [UIColor whiteColor]; [self.view addSubview:_descriptionTextView];

Effettuando un Run del progetto otteremo la seguente schermata:

Figura 38: Schermata dell’applicazione dopo l’aggiunta dei TextField e della TextView

Focalizziamo l’attenzione sulla dichiarazione dei TextFields; le property di maggior interesse che abbiamo utilizzato sono le seguenti:

• placeholder: consente l’inserimento di un testo iniziale all’interno del TextField.

• borderStyle: definisce lo stile del bordo del TextField.

• clearButtonMode: mostra una X alla destra del TextField che se toccata cancella il testo inserito.

In questo esempio abbiamo utilizzato solo una piccola parte di impostazioni che è possibile effettuare su un oggetto UITextField. Se il lettore fosse interessato ad approfondire l’argomento può consulatre la documentazione relativa.

Il codice per la dichiarazione della TextView è di ovvia comprensione; anche in questo caso se il lettore vuole approfondire la struttura di tale componente può consultare la documentazione.

Page 43: Programmazione iOS

  43  

La gestione del testo in una applicazione iOS

Passiamo adesso ad analizzare il comportamento dell’applicazione: come possiamo vedere toccando uno dei due TextField o la TextView apparirà la tastiera nella parte bassa dello schermo. Risultano evidenti due problemi: non riusciamo a mandare via la tastiera anche se tocchiamo il tasto return e la tastiera copre parte dell’area di testo della TextView non consento all’utente di leggere completamente i caratteri digitati. Andiamo a risolverli.

Rimuoviamo la tastiera dopo aver terminato l’inserimento del testo

Affinchè possa essere implementata questa funzionalità abbiamo bisogno di implementare, nel nostro InsertViewController, due delegati: UITextFieldDelegate e UITextViewDelegate. Per il momento tralasciamo la definizione approfondita di cosa sia un delegato (che andremo a trattare più avanti); ci basti sapere che, implementando il delegato del TextField e dellaTextView saremo a conoscenza degli eventi associati a tali oggetti. Questo permetterà al nostro InsertViewController di avere nuovi metodi per gestire gli eventi proprio su questi oggetti.

Per far ciò spostiamoci nel file InsertViewController.h ed associamo alla nostra classe i delegati necessari nel seguente modo: @interface  InsertViewController : UIViewController &ltUITextViewDelegate, UITextFieldDelegate>

Adesso la nostra classe è pronta per ricevere gli eventi dei TextField e della TextView. Non ci resta che associare a questi due elementi il nostro InsertViewController come delegatoandando ad agire sulla property  delegate degli oggetti nel seguente modo (aggiungendo rispettivamente questa linea prima dell’addSubView di ogni oggetto):

_nameTextField.delegate = self;      _originTextField.delegate = self;      _descriptionTextView.delegate = self;

Come prima cosa implementiamo la possibilità di rimuovere la tastiera quando l’utente ha finito di modificare il testo. Avendo implementato il delegato UITextFieldDelegate adesso abbiamo a disposizione nel FirstViewController il metodo textFieldShouldReturn che ci consentirà di capire quando l’utente tocca il tasto return della tastiera dopo aver inserito del testo all’interno di un TextField. Andiamo dunque ad inserire il seguente codice all’interno del file FirstViewController.m: - (BOOL)textFieldShouldReturn:(UITextField *)textField  {   [textField resignFirstResponder];   return  YES;  }

Page 44: Programmazione iOS

  44  

Quando un utente tocca un TextField, quest’ultimo entra nello stato di first responder ovvero è quell’oggetto che per primo riceve l’interazione con l’utente. Come possiamo vedere, all’interno del metodo di delegato, andiamo proprio ad agire su tale stato invocando il metodoresignFirstResponder sul TextField che notificherà all’applicazione l’uscita dallo stato first responder. Da sottolineare è il fatto che l’oggetto UITextField che ci viene passato come parametro al metodo textFieldShouldReturn: è sempre il textField che è in stato first responder.

Passiamo adesso alla TextView. In questo caso andremo ad utilizzare il metodo di delegatotextView:  shouldChangeTextInRange:  replacementText: come segue:

- (BOOL)textView:(UITextView *)textView shouldChangeTextInRange:(NSRange)range   replacementText:(NSString *)text {   if() {   [textView resignFirstResponder];   return  NO;   }   return  YES;  }

Il metodo di delegato sopra riportato, viene richiamato ogni qual volta l’utente digiti un tasto sulla tastiera virtuale. All’interno del metodo andiamo a contrallare se l’ultimo carattere inserito è uguale a \n, ovvero il carattere di a capo ottenuto toccando il tasto return sulla tastiera, invocando il metodo isEqualToString sulla stringa text. Se il riscontro restituisce esito positivo, invochiamo il metodo resignFirstResponder sulla TextView.

Tengo a precisare il fatto che la soluzione mostrata è una soluzione non ottimale, in quanto otterremo il comportamento voluto (mandare via la tastiera), ma inficeremo il comportamento standard della TextView. Questo perchè il corretto dismiss della tastiera dovrà essere eseguito con concetti (bottoni ed eventi associati) che andremo ad affrontare nelle prossime lezioni.

Spostiamo il contenuto della view per consentire la lettura del testo inserito nella TextView

Affinchè il testo nella TextView sia leggibile durante la digitazione è necessario traslare il contenuto di tutta la view verso l’alto. Per far ciò andremo ad utilizzare altri due metodi di delegato della TextView: textViewDidBeginEditing: (richiamo quando la TextView diventa first responder) e il metodo textViewDidEndEditing: (richiamato quando viene richiamato il metodoresignFirstResponder sulla TextView). - (void)textViewDidBeginEditing:(UITextView *)textView  {   CGAffineTransform translateUp = CGAffineTransformMakeTranslation(0.0, -150.0);       [UIView beginAnimations:nil context:nil];   [UIView setAnimationDuration:0.2];   self.view.transform = translateUp;   [UIView commitAnimations];  

Page 45: Programmazione iOS

  45  

}      - (void)textViewDidEndEditing:(UITextView *)textView  {   CGAffineTransform translateUp = CGAffineTransformMakeTranslation(0.0, 0.0);       [UIView beginAnimations:nil context:nil];   [UIView setAnimationDuration:0.2];   self.view.transform = translateUp;   [UIView commitAnimations];  }

La spiegazione del codice all’interno di questi due metodi esula dai contenuti di questa guida, ma è stato tuttavia inserito, per permettere un corretto utilizzo dell’applicazione. Al lettore basti sapere che la funzione CGAffineTransformMakeTranslation(tx,ty) riceve come parametri due valori di tipo CGFloat che indicano di quanti pixel sarà spostato l’oggetto, rispettivamente sull’asse x e sull’asse y. Tale trasformata non modificherà il frame dell’oggetto, ma applicherà su di esso solamente una trasformata e quindi per riportarlo nella posizione originaria dovremo applicare la trasformata CGAffineTransformMakeTranslation(0.0,  0.0).

La creazione dei pulsanti in un’applicazione iOS

In questa lezione andremo ad introdurre uno degli oggetti più utilizzati nello sviluppo di applicazioni iOS: l’UIButtonn. Questo elemento ha il preciso compito di intercettare i diversi tipi di eventi di tocco dell’utente ed eventualmente richiamare diversi metodi per ogni tipologia di evento ricevuto..

Passiamo adesso all’implementazione di tali oggetti all’interno del nostro progetto. Portiamoci nel filee InsertViewController.hhe dichiariamo due bottoni come attributi di classe nel seguente modo:: UIButtonn*_resetButton; UIButtonn*_addButton;

Portiamoci adesso nel filee InsertViewController.mme nel solito metodo di init (dopo la dichiarazione dellaa TextViewwche abbiamo visto nella precedente lezione) andiamo ad inserire il seguente codice:: _resetButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; _resetButton.frame = CGRectMake(10, 370, 140, 30); [_resetButton setTitle:@"Reset" forState:UIControlStateNormal]; [_resetButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal]; [self.view addSubview:_resetButton]; _addButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; _addButton.frame = CGRectMake(160, 370, 140, 30); [_addButton setTitle:@"Inserisci" forState:UIControlStateNormal]; [_addButton setTitleColor:[UIColor blackColor] forState:UIControlStateNormal];

Page 46: Programmazione iOS

  46  

[self.view addSubview:_addButton];

Come possiamo vedere, la dichiarazionee è diversa dalle altre dichiarazioni che abbiamo incontrato fino ad ora. Qesto perchè la classee UIButton  ci mette a disposizione il metodo di classe buttonWithType  che consente di specificare il tipo di bottone ottenendo, con poco sforzo, un oggetto graficamente gradevole. Se avessimo usato gli usuali metodi di allocazione e inizializzazionee allocc  e init  avremo dovuto effettuare un po’ più di lavoro per avere una resa grafica del bottone migliore.

Continuando con l’analisi del codice, nella linea successiva, viene impostato il frame  dei bottoni e viene impostato il titolo (che è la scritta presente sopra il bottone) ed il colore del testo del bottone quando quest’ultimo si trova nello stato UIControlStateNormal  (che rappresenta lo stato in cui si trova il bottone quando non riceve eventi di tocco).

Effettuando un Run del progetto vedremo i bottoni posizionati correttamente nella parte bassa dello schermo, ma toccandoli non accadrà niente. Questo è ovvio perchèè non abbiamo associato a tali bottoni nessun metodo in risposta ad un evento di tocco.

Quindi, la prima cosa da fare è dichiarare due metodi nel file InsertViewController.h: uno da associare al _resetButton  e uno all’_addButton. Inseriamo dunque il seguente codice dopo la dichiarazione degli attributi della classe: -(void)addButtonTapped; -(void)resetButtonTapped;

A questo punto andiamo ad implementare tali metodi all’interno della classeInsertViewController.m: - (void)addButtonTapped{ NSLog(@"Button TAPPED !!"); } - (void)resetButtonTapped{ _originTextField.text = @""; _nameTextField.text = @""; _descriptionTextView.text = @""; }

Sui metodi non c’è da dire molto: nel metodo da associare all’_addButton  per adesso mettiamo solo una stampa sulla console (in un secondo momento salveremo un nuovo oggetto da inserire nella lista), mentre nel metodo da associare al _resetButton  semplicemente inseriamo una stringa vuota nei due TextField e nella TextView.

A questo punto non ci resta che associare tali metodi ai bottoni. Sempre nella classeInsertViewController.m e dopo la definizione del frame dei bottoni aggiungiamo rispettivamente: [_resetButton addTarget:self action:@selector(resetButtonTapped) forControlEvents:UIControlEventTouchUpInside]; [_addButton addTarget:self action:@selector(addButtonTapped) forControlEvents:UIControlEventTouchUpInside];

Il metodo utilizzato consta di tre parametri che analizziamo singolarmente:

Page 47: Programmazione iOS

  47  

• addTarget: rappresenta l’oggetto target a cui il bottone invierà il messaggio in seguito alla ricezione di un evento di tocco (nel nostro caso la classe InsertViewController).

• action: rappresenta il metodo che deve essere invocato dopo la ricezione dell’evento di tocco. Per far ciò usiamo il costrutto @selector  che serve a richiamare un metodo appartenente ad una classe.

• forControlEvent: rappresenta il tipo di evento di tocco. Nel nostro caso abbiamo scelto l’evento UIControlEventTouchUpInside  che rappresenta l’evento di tocco associato alla pressione del bottone e rilascio sempre all’interno dell’area del bottone. Nulla vieta al lettore di definire altri metodi da associare al bottone per diversi tipi di evento.che rappresenta l’evento di tocco associato alla pressione del bottone e rilascio sempre all’interno dell’area del bottone. Nulla vieta al lettore di definire altri metodi da associare al bottone per diversi tipi di evento.

 

La creazione di una toolbar in un’applicazione iOS

Avendo chiari i concetti associati ai bottoni, adesso possiamo implementare un dismisssdella tastiera ottimale utilizzando un oggetto di tipoo UIToolbar. Una toolbar è un controllore che permette di aggiungere su di essa uno o più pulsanti. Quest’ultimi non sono oggetti di tipooUIButton, ma bensì oggetti di tipoo UIBarButtonItem, specifici per lee toolbar.

Dovremo utilizzare la property  inputAccessoryView  della TextView per inserire laa toolbar con sopra il pulsante che effettuerà l’azione di dismiss sulla tastiera..

Riportiamoci dunque nel file InsertViewController.h ed inseriamo il seguente metodo: -(void)dismissKeyboard;

A questo punto selezioniamo il file InsertViewController.m ed inseriamo l’implementazione del metodo che effettuerà il dismiss della tastiera: - (void)dismissKeyboard{ if ([_descriptionTextView isFirstResponder]) { [_descriptionTextView resignFirstResponder]; } }

Adesso, sempre nell’InsertViewController.m, all’interno del solito metodo di init, inseriamo il seguente codice prima della dichiarazione della TextView:

Page 48: Programmazione iOS

  48  

UIToolbar *toolbar = [[UIToolbar alloc] initWithFrame:CGRectMake(0, 0, 320, 44)]; toolbar.barStyle = UIBarStyleBlackTranslucent; UIBarButtonItem *doneButton =[[UIBarButtonItem alloc] initWithTitle:@"Fatto" style:UIBarButtonItemStyleDone target:self action:@selector(dismissKeyboard)]; toolbar.items = [NSArray arrayWithObjects:doneButton, nil];

La differenza tra un oggetto UIButton  ed un o UIBarButtonItem  è che quest’ultimo necessita meno linee di codice per essere dichiarato e personalizzato ed offre già, nel metodo dimeno linee di codice per essere dichiarato e personalizzato ed offre già, nel metodo di init, la possibilità di inserire l’azione da associare al bottone.

A questo punto, come detto precedentemente, inseriamo sopra la tastiera, grazie alla property  accessoryView, la toolbar con la seguente linea di codice da inserire prima dell”addSubView  della TextView :

_descriptionTextView.inputAccessoryView  =  toolbar;  

Effettuando adesso un Run del progetto e toccando la TextView avremo la seguente schermata:

Page 49: Programmazione iOS

  49  

La creazione di pulsanti personalizzati in un’applicazione iOS

Completiamo l’interfaccia grafica dell’InsertViewController andando ad aggiungere i bottoni per la selezione dell’immagine.

Andiamo nel file InsertViewController.h ed inseriamo le seguenti linee di codice: NSArray *_imageButtonArray; NSMutableArray *_buttonArray;

All’interno del primo array inseriremo le immagini che verranno mostrate nei bottoni, mentre il secondo array conterrà gli oggetti di tipo UIButton che creeremo in seguito. Come possiamo vedere i due array sono dichiarati utilizzando due classi diverse: NSArray che dichiarerà un array statico nel quale non potremo aggiungere nuovi elementi o toglierli, mentreNSMutableArray dichiarerà un array dinamico.

Passiamo dunque al file InsertViewController.m e nel metodo di init usuale inseriamo le seguenti linee di codice:

_imageButtonArray = [[NSArray alloc]initWithObjects:[UIImage imageNamed:@"pera.png"], [UIImage imageNamed:@"arancia.png"], [UIImage imageNamed:@"mela.png"],nil]; _buttonArray = [[NSMutableArray alloc]init]; for (int i =0; i< 3; i++) { UIButton * button = [UIButton buttonWithType:UIButtonTypeCustom]; button.frame = CGRectMake(20+(100*i), 280, 80, 80); [button setImage:[_imageButtonArray objectAtIndex:i] forState:UIControlStateNormal]; [button addTarget:self action:@selector(selectImageButton:) forControlEvents:UIControlEventTouchUpInside]; button.tag = i; [self.view addSubview:button]; [_buttonArray addObject:button]; }

Come prima cosa inseriamo all’interno dell’array _imageButtonArray le UIImage che utilizzeremo per i bottoni. Questi ultimi sono creati e posizionati tramite un ciclo for ed infine inseriti nell’array _buttonArray. Una prima particolarità che è possibile notare è che il metodo associato al bottone presenta i due punti (“:”) finali: in questo modo potremo sapere, all’interno del metodo, quale oggetto ha notificato l’evento.

Leggendo il codice sopra riportato possiamo notare che il bottone viene dichiarato di tipoUIButtonTypeCustom (che rappresenta un’inizializzazione più generica) ed infine agiamo sullaproperty  tag associandole il valore usato come counter nel ciclo (vedremo tra poco come utilizzarlo).

A questo punto è necessario creare il metodo associato ai bottoni. Andiamo dunque a dichiarare il metodo necessario nel file InsertViewController.h nel seguente modo: - (void)selectImageButton:(UIButton *)sender;

Page 50: Programmazione iOS

  50  

Spostiamoci poi nel file InserViewController.h ed importiamo il framework QuartzCore come segue (è necessario per accedere alla property  layer dell’oggetto UIButton):

#import <QuartzCore/QuartzCore.h>

Infine inseriamo l’implementazione del metodo: - (void)selectImageButton:(UIButton *)sender{ for (UIButton * button in _buttonArray) { if (button.tag == sender.tag) { button.layer.borderColor = [UIColor blueColor].CGColor; button.layer.borderWidth = 3.0; [button setSelected:YES]; } else{ button.layer.borderWidth = 0.0; [button setSelected:NO]; } } }

Quello che andiamo a fare nel metodo è semplicemente recuperare dall’array l’oggettoUIButton che l’utente ha toccato (utilizzando la property  tag), aggiungerci un bordo colorato ed infine, invocando il metodo setSelected con parametro YES cambiamo lo stato del bottone inselected (questo ci servirà tra poco per la creazione dell’oggetto frutto).

Effettuando un Run del progetto otteremo la schermata definitiva:

Page 51: Programmazione iOS

  51  

Adesso vedremo come creare l’oggetto fruit per salvare le informazioni inserite nel modulo.

Come salvare le informazioni di un modulo

A questo punto è necessario salvare i dati che l’utente andrà ad inserire all’interno di un nuovo oggetto che andremo a creare. Seguiamo la stessa procedura vista nelle lezioni precedenti per l’aggiunta di una nuova classe al progetto e questa volta scegliamo, come classe da cui ereditare NSObject. Infine salviamo la nuova classe con il nome Fruit.

Come prima cosa andiamo nel file Fruit.h e dichiariamo gli attributi di classe che rappresenteranno le informazioni inserite dall’utente nel modulo di creazione: NSString * _name; NSString * _origin; NSString * _description; UIImage * _image;

Una classe che eredita da NSObject possiede il metodo init come costruttore, ma dato che a noi serve un init a cui passare dei parametri ne dichiariamo uno noi nel seguente modo:

- (id)initWithName:(NSString*)name origin:(NSString*)origin description:(NSString*)description image:(UIImage*)image;

Adesso spostiamoci nel file Fruit.m ed inseriamo il seguente codice di implementazione del metodo di init creato:

- (id)initWithName:(NSString *)name origin:(NSString *)origin description:(NSString *)description image:(UIImage *)image{ self = [super init]; if (self){ _name = [[NSString alloc]initWithString:name]; _origin = [[NSString alloc]initWithString:origin]; _description = [[NSString alloc]initWithString:description]; _image = [[UIImage alloc]initWithCGImage:image.CGImage]; } return self; }

Adesso la nostra classe Fruit è terminata. L’ultima operazione che dobbiamo compiere è la creazione di un oggetto Fruit tutte le volte che l’utente, dopo aver inserito i campi, preme il bottone Inserisci. Spostiamoci dunque nella classe InsertViewController.m e all’interno del metodo addButtonTapped inseriamo il seguente codice:

Page 52: Programmazione iOS

  52  

 int index; for (int i = 0; i < 3; i++) { UIButton * button = [_buttonArray objectAtIndex:i]; if (button.selected == YES) { index = i; button.layer.borderWidth = 0.0; [button setSelected:NO]; } } UIImage * fruitImage = [_imageButtonArray objectAtIndex:index]; Fruit * fruitObject= [[Fruit alloc]initWithName:_nameTextField.text origin:_originTextField.text description:_descriptionTextView.text image:fruitImage]; _nameTextField.text = @""; _originTextField.text = @""; _descriptionTextView.text = @"";

All’interno del ciclo for cerchiamo l’indice del bottone, contenente l’immagine del frutto, che avevamo selezionato e ricaviamo l’immagine associata. A questo punto creiamo l’oggettoFruit utilizzando il metodo di init precedentemente creato e resettiamo i campi di testo.

Con queste linee di codice abbiamo creato un oggetto Fruit e nelle prossime lezioni vedremo come passare tale oggetto alla classe ListViewController.

 

Come notificare errori di compilazione

Nella lezione precedente abbiamo creato l’oggetto Fruit ed associato al bottone addButton la creazione di un’istanza di tale oggetto. Andando a leggere il metodo di init per l’oggetto Fruitvedremo che non viene effettuato nessun controllo su eventuali valori nil. Questa mancanza, nel caso in cui l’utente inserisca un frutto con un campo di testo non compilato, causerà uncrash dell’applicazione.

Andiamo dunque a risolvere il problema andando a controllare, prima dell’inserimento, che tutti i campi siano correttamente compilati (compresa la selezione dell’immagine del frutto). Se anche uno di questi campi è stato lasciato vuoto mostreremo una UIAlertView che notificherà il problema all’utente.

Andiamo quindi nel file ListViewController.m e posizioniamoci nel metodo addButtonTapped. Sostituiamo tutto il codice precedente alla creazione dell’oggetto Fruit come segue:

Page 53: Programmazione iOS

  53  

int index; BOOL imageSelected = NO; for (int i = 0; i < 3; i++) { UIButton * button = [_buttonArray objectAtIndex:i]; if (button.selected == YES) { index = i; imageSelected = YES; button.layer.borderWidth = 0.0; [button setSelected:NO]; } } if (imageSelected == NO || _nameTextField.text == nil || _originTextField.text == nil || _descriptionTextView.text == nil) { UIAlertView * alertView = [[UIAlertView alloc]initWithTitle:@"Attenzione!" message:@"Tutti i campi sono obbligatori" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"Ok", nil]; [alertView show]; return; } UIImage * fruitImage = [_imageButtonArray objectAtIndex:index];

Come prima cosa controlliamo, all’interno del ciclo for, la presenza di un bottone immagine selezionato e salviamo l’esito del controllo nella variabile imageSelected.

Terminato il ciclo, utilizziamo un’istruzione if per verificare la presenza di campi vuoti: se la condizione viene soddisfatta creiamo un oggetto UIAlertView e lo mostriamo a schermo con il metodo show. Come possiamo vedere dall’init della alert, passiamo i valori nil sia per per ilcancelButton sia per il delagato. Questo perchè non abbiamo bisogno di un cancelButton (dato che la nostra alert avrà un solo pulsante Ok) e nemmeno del delegato in quanto, in presenza di un solo pulsante, l’azione di default associata alla pressione di tale pulsante effettuerà ildismiss della alert. Infine terminiamo il flusso dell’applicazione richiamando un return(evitando così la creazione dell’oggetto Fruit).

Effettaundo un Run del progetto e lasciando almeno un campo vuoto nel modulo di inserimento il risultato sarà il seguente:

Page 54: Programmazione iOS

  54  

Facciamo comunicare due interfacce: protocol e delegate

Nella lezione in cui abbiamo introdotto gli oggetti UITextField e UITextView abbiamo parlato di delegati. Di seguito andremo ad approfondire l’argomento.

Iniziamo con il dire che la delegazione è un pattern molto utilizzato nella programmazione ad oggetti, nel quale un oggetto invia periodicamente messaggi ad un altro oggetto detto delegatoinformandolo per esempio di un cambio di stato o di un evento che si è verificato. A questo punto il delegato potrà eseguire alcune operazioni e per esempio aggiornare la sua interfaccia. Di conseguenza, il patern delegation, permette la comunicazione di due oggetti tramite un interfaccia detta protocollo.

Un protocollo, in Objective C, altro non è che un unico file di estensione .h nel quale vengono dichiarati alcuni metodi. Il compito del delegato di un oggetto è proprio quello di implementare i metodi definiti all’interno del protocollo definendo quindi un comportamento associato ad un particolare evento dell’eggetto delegante.

Quello che abbiamo fatto nella lezione relativa ai TextField e TextView è stato proprio quello di implementare i protocolli di delegato di tali oggetti nella classe InsertViewController per poi utilizzare i metodi definiti nel protocollo stesso per effettuare il dismiss della tastiera.

Page 55: Programmazione iOS

  55  

Quello che abbiamo fatto è stato quello di implementare delegati definiti già nell’SDK di sviluppo, ma nulla vieta allo sviluppatore di scrivere i propri protocolli da implementare in altre classi. Quello che faremo adesso è proprio questo: definire un protocollo per mettere in comunicazione l’InsertViewController (che si occupa dell’inserimento dei dati) ed ilListViewController (che si occuperà di mostrare i dati inseriti).

Definiamo il protocollo Iniziamo creando un nuovo file di progetto, scegliamo come tipo di classe Objective-C protocol e nominiamo il nuovo file InsertDelegate. Come potremo vedere verrà creato un unico file di estensione .h nel quale inseriremo, dopo aver importato la classe Fruit, la seguente dichiarazione:

-(void)insertNewFruit:(Fruit*)fruit;

Inseriamo solo il metodo insertNewFruit: dato che ci interessa notificare alla classeListViewController l’avvenuto inserimento di un nuovo frutto.

Dichiariamo un attributo delegate nella classe InsertViewController Adesso spostiamoci nel file InsertViewController.h ed andiamo a dichiarare l’oggetto delegate(come attributo di classe) al quale verrà associato, al momento dell’allocazione nell’AppDelegate, un oggetto di tipo ListViewController (ricordiamoci prima di importare la classe FruitDelegate):

__weak id <InsertDelegate> _delegate;

Come possiamo vedere nella dichiarazione dell’attributo castiamo l’oggetto id al tipo del delegato InsertDelegate creato precedentemente. Infine dichiariamo la property sull’oggetto_delegate nel seguente modo:

@property(nonatomic,weak) id <InsertDelegate> delegate;

Terminiamo il lavoro sulla classe InsertViewController (per quanto riguarda la dichiarazione del delegato) effettuato il synthesize della property appena creata nel file .m come segue:

@synthesize  delegate = _delegate;

A questo punto spostiamoci nel metodo addButtonTapped (sempre nella classeInsertViewController.m) e, dopo la creazione dell’oggetto Fruit inseriamo la seguente linea di codice:

[self.delegate insertNewFruit:fruitObject];

Con questa linea notificheremo al delegate (nel nostro caso un ogetto di tipoListViewController) che è stato creato un nuovo oggetto Fruit passandolo come parametro al metedo insertNewFruit: che il delegate andrà ad implementare.

Implementiamo il delegato nella classe ListViewController A questo punto spostiamoci nel file ListViewController.h ed implementiamo il protocollo che abbiamo creato precedentemente nel seguente modo (dopo aver importato la classeInsertDelegate):

@interface  ListViewController : UIViewController <InsertDelegate>

Page 56: Programmazione iOS

  56  

Posizioniamoci adesso nel file ListViewController.m ed implementiamo il metodo di delegato come segue: - (void)insertNewFruit:(Fruit *)fruit{   NSLog(@"Nuovo frutto!");  }

Associamo il ListViewController come delegato dell’InsertViewController Adesso l’infrastruttura è completa, non ci resta che associare all’oggettoInsertViewController l’oggetto ListViewController come delegato. Spostiamoci dunque nell’AppDelegate.m e alla fine della dichiarazione dell’oggetto InsertViewControlleraggiungiamo la seguente linea di codice:

insertViewController.delegate = listViewController;

A questo punto, effettuando un Run del progetto, dopo aver compilato tutti i campi e premuto il bottone Inserisci vedremo apparire sulla console la scritta: Nuovo frutto!.

Creiamo la struttura necessaria per salvare i dati nelListViewController L’ultima operazione che dobbiamo eseguire è quella di salvare, all’interno dell’oggettoListViewController ogni oggetto Fruit creato. Dichiariamo come attributo di classe un arraynel file ListViewController.h: NSMutableArray * _itemsList;

Effettuiamo poi l’allocazione dell’array nel metodo initWithNibName:  bundle: nel fileListViewController.m:

_itemsList = [[NSMutableArray alloc]init];

Infine, all’interno del metodo insertNewFruit: di delegato inseriamo la seguente linea di codice: [_itemsList addObject:fruit];

Nella prossima lezione andremo a vedere come mostrare i dati inseriti usando l’oggettoUITableView.

L’oggetto UITableView: come mostrare i dati

Iniziamo a dare forma al nostro ListViewController (che ricordiamo mostrerà i dati inseriti) inserendo un nuovo oggetto: l’UITableView. Quest’ultimo altro non è che una tabbella e risulta essere uno degli oggetti maggriormente utilizzato nello sviluppo di applicazioni iOS. Risulta anche essere uno strumento molto potente in quanto, in maniera relativamente semplice, avremo la possibilità di mostrare anche un grande numero di informazioni in maniera ordinata.

Struttura di un oggetto UITableViewCell

Ogni singola entry della tabella, che mostrerà le informazioni di un singolo elementi, è un oggetto di tipo UItableViewCell opportunamente creato. I

Page 57: Programmazione iOS

  57  

principali attributi che sfrutteremo, appartanenti alla classe UITableViewCell, sono i seguenti:

• textLabel: è una label nella quale inserire il contenuto principale di una cella e che nel nostro caso conterrà il valore associata alla property  name dell’oggetto Fruit.

• detailTextLabel: è una label aggiuntiva (e che avrà un font di dimensione inferiore rispetto alla precedente label) di una cella e che nel nostro caso conterrà il valore associata alla property  origin dell’oggetto Fruit.

• imageView: è un oggetto di tipo UIImageView che conterrà l’immagine della cella e che nel nostro caso sarà uguale alla property  image dell’oggetto Fruit.

I contenuti all’interno di una tabella sono organizzati gerarchicamente utilizzando le sezioni e le righe. L’oggetto NSIndexPath racchiude per ogni elemento della tabella, la sua sezione e la sua riga offrendo così un indice univoco. Anche la TableView, oltre ad avere un delegatecome gli oggetti UITextField e UITextView precedentemente incontrati, possiede anche undata source ovvero una classe che fornirà alla tabella i dati da impaginare; la classe data source dovrà implementare il protocollo UITableViewDataSource mettendo così a disposizione dello sviluppatore dei metodi che consentiranno il popolamento della tabella.

Implementiamo la tabella nella classe ListViewController Fatta questa breve introduzione teorica vediamo come inserire la tabella nella nostra applicazione. Come prima cosa, dato che dobbiamo impaginare le informazioni immagazzinate in oggetti di tipo Fruit è necessario definire delle property sugli attributi della classe per concederne l’accesso. Andiamo quindi nel file Fruit.h ed inseriamo le seguenti property:

@property  (nonatomic,strong) NSString* name;  @property  (nonatomic,strong) NSString* origin;  @property  (nonatomic,strong) NSString* description;  @property  (nonatomic,strong) UIImage* image;

Spostiamoci poi nel file Fruit.m ed effettuiamo il synthesize delle property: @synthesize  name = _name;  @synthesize  origin = _origin;  @synthesize  description = _description;  @synthesize  image = _image;

Concluse queste operazioni iniziali andiamo a dichiarare un oggetto di tipo UITableViewall’interno del file ListViewController.h:

UITableView *_tableView;

Passiamo poi alla sua allocazione all’interno del metodo initWithNibName:  boundle: nel seguente modo:

_tableView = [[UITableView alloc]initWithFrame:CGRectMake(0, 0, 320, 480) style:UITableViewStylePlain];   _tableView.dataSource = self;   _tableView.delegate = self;  [self.view addSubview:_tableView];

Page 58: Programmazione iOS

  58  

Come possiamo vedere, nell’inizializzazione della tabella, abbiamo anche definito il suo style; i due tipi di stile associabili alla tabella sono UITableViewStylePlain e UITableViewStyleGrouped(vedremo in seguito le differenze tra le rese grafiche dei due stili). Abbiamo poi scelto di associare come dataSource e come delegate la nostra stessa classe. Nel fileListViewController.h andiamo ad implementare il protocollo UITableViewDataSource e il protocollo UITableViewDelegate nel seguente modo: @interface  ListViewController : UIViewController <InsertDelegate, UITableViewDataSource,UITableViewDelegate>

Adesso abbiamo a dispozizione i metodi necessari per poter impaginare i dati all’interno della tabella. I metodi obbligatori per poter correttamente mostrare a schermo delle informazioni all’interno della tabella sono i seguenti:

• numberOfSectionsInTableView:: è il metodo che ritorna il numero di sezioni possedute dalla tabella

• tableView:  numberOfRowsInSection:: è il metodo che, dato l’indice di una sezione, ritorna il numero di righe associate a quella sezione

• tableView:  cellForRowAtIndexPath:: è il metodo che, dato un oggetto di tipo NSIndexPath(quindi l’indice univoco per l’elemento della tabella), si occupa di mostrare i dati di un elemento.

Tali metodi verranno invocati in maniera automatica quando la tabella verrà istanziata. Andiamo adesso ad implementare tali metodi. Spostiamoci nel file ListViewController.m ed inseriamo il seguente codice: - (int)numberOfSectionsInTableView:(UITableView *)tableView{   return  1;  }      - (int)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section{   return  [_itemsList count];  }

Al primo metodo facciamo tornare valore 1 perchè vogliamo una sola sezione per la nostra tabella. Al secondo metodo facciamo tornare, come numero di righe, la lunghezza dell’array_itemsList contenente gli oggetti Fruit.h creati nella classe InsertViewController.

Adesso mostriamo il codice relativo al metodo che si occupa del popolamento dei dati all’interno della tabella:

- (UITableViewCell*)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath*) indexPath{   static  NSString *CellIdentifier = @"Cell";   UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];       if  (cell == nil) {   cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];   }       cell.textLabel.text = [[_itemsList objectAtIndex:indexPath.row]name];  

Page 59: Programmazione iOS

  59  

cell.detailTextLabel.text = [[_itemsList objectAtIndex:indexPath.row]origin];   cell.imageView.image = [[_itemsList objectAtIndex:indexPath.row]image];   return  cell;  }

Prima di analizzare il codice è opportuno fare una breve panoramica sul funzionameto della creazione delle celle. Il funzionamento standard di una tabella è quello di allocare in memoria tanti oggetti UITableViewCell quante sono le celle visibili della tabella nello schermo del dispositivo. Tutte le volte che l’utente effettua uno scroll della tabella, in automatico viene richiamo il metodo tableView:  cellForRowAtIndexPath: che non andrà a creare altri oggetti di tipo UITableViewCell, ma bensì utilizzerà quelli precedentemente creati andando solamente a cambiarne il contenuto.

Passando al codice, per implementare questo comportamento, definiamo un CellIdentifier di tipo NSString che verrà utilizzato per l’acquisizione della cella usando il metododequeueReusableCellWithIdentifier che implementerà la politica di riuso degli oggetti cella. Con quel metodo viene ritornato un oggetto di tipo UITableViewCell utilizzando ilCellIdentifier precedentemente creato e se la cella risulta uguale a nil (quindi la cella non è ancora stata creata) la allochiamo e ne inseriamo il contenuto adando a reperire le informazioni all’interno dell’array _itemsList utilizzando l’indice univoco IndexPath.row (ovvero il numero di riga con valore della sezione pari ad 1).

Quando l’utente effettuerà uno scroll della tabella la cella ritornata dal meotododequeueReusableCellWithIdentifier sarà una cella valida e quindi il flusso d’esecuzione non entrerà all’interno del blocco condizionale if e verrà effettuato solo un aggiornamento dei contenuti.

Adesso l’infrastruttura necessaria per il corretto funzionamento della tabella è terminata. Dobbiamo però aggiungere sempre nel file ListViewController.m all’interno del metodoinsertNewFruit: (dopo l’aggiunta del nuovo oggetto Fruit nell’array) la seguente linea di codice:

[_tableView reloadData];

Questa chiamata è necessaria perchè, come abbiamo detto prima, i metodi per il popolamento dei dati vengono chiamati quando la tabella viene allocata e l’array dei frutti inseriti ha lunghezza zero. E’ dunque necessario, dopo ogni inserimento, invocare il metodo reloadDatasulla tabella per aggiornare i dati mostrati.

Finalmente, effettaundo un Run del progetto (dopo l’inserimento di qualche frutto), avremo il seguente risultato:

   

Page 60: Programmazione iOS

  60  

Creiamo delle celle custom per la nostra tabella

Come abbiamo visto nella lezione precedente ogni oggetto UITableViewCell ci mette a disposizione due label per l’inserimento del contenuto che verrà mostrato nella cella. Nel nostro caso però abbiamo bisogno di una terza label in quanto dobbiamo mostrare anche il contenuto salvato all’interno dell’attributo description della classe Fruit.

Per far ciò abbiamo bisogno di creare una cella custom ottenuta creando una nuova classe che eredita dalla classe UITableViewCell. Seguiamo dunque la solita procedura per l’aggiunta di un nuovo file al progetto; quando ci viene richiesta la classe da cui ereditare, scegliamo proprio UITableViewCell e nominiamo tale fileFruitCell. Ereditando da tale classe erideteremo anche le label utilizzate nella lezione precedente per mostrare i dati relativi al nome e alla provenienza del frutto. Sarà dunque sufficiente dichiarare una nuova label (nel file FruitCell.h per la descrizione), nel seguente modo:

@interface  FruitCell  :  UITableViewCell{          UILabel  *  _descriptionLabel;  }  @property  (nonatomic,strong)UILabel  *  descriptionLabel;    @end  

Spostandosi poi nel file FruitCell.m inseriamo il seguente codice:

@synthesize  descriptionLabel;    -­‐  (id)initWithStyle:(UITableViewCellStyle)style  reuseIdentifier:(NSString  *)reuseIdentifier  {          self  =  [super  initWithStyle:style  reuseIdentifier:reuseIdentifier];          if  (self)  {                  descriptionLabel  =  [[UILabel  alloc]initWithFrame:CGRectMake(78,  35,  320,  50)];                  descriptionLabel.backgroundColor  =  [UIColor  clearColor];                  descriptionLabel.textColor  =  [UIColor  blackColor];                  descriptionLabel.font  =  [UIFont  systemFontOfSize:12];                  [self  addSubview:descriptionLabel];          }          return  self;  }  

Come possiamo vedere utilizziamo il metodo initWithStyle:  reuseIdentifier:, ereditato dalla classe UITableViewCell, per allocare ed inizializzare ladescriptionLabel con i metodi visti nelle lezioni precedenti. Dato che abbiamo aggiunto una nuova label, l’altezza di default delle celle non è più sufficiente e quindi andremo ad utilizzare il metodo tableView:  heightForRowAtIndexPath: proprio per questo scopo (il metodo è sempre definito nel protocollo UITableViewDataSource). Andiamo dunque nel file ListViewController.m e come prima cosa effettuiamo l’import della classe FruitCell. Implementiamo adesso il metodo che andrà a variare l’altezza delle celle nel seguente modo:

-­‐  (CGFloat)tableView:(UITableView  *)tableView  heightForRowAtIndexPath:(NSIndexPath  *)indexPath{          return  70;  }  

Adesso spostiamoci nel metodo tableView:  cellForRowAtIndexPath: ed aggiorniamo il codice al suo interno come segue:

Page 61: Programmazione iOS

  61  

static  NSString  *CellIdentifier  =  @"Cell";    FruitCell  *cell  =  [tableView  dequeueReusableCellWithIdentifier:CellIdentifier];  if  (cell  ==  nil)  {      cell  =  [[FruitCell  alloc]  initWithStyle:UITableViewCellStyleSubtitle  reuseIdentifier:CellIdentifier];  }  cell.textLabel.text  =  [[_itemsList  objectAtIndex:indexPath.row]name];  cell.detailTextLabel.text  =  [[_itemsList  objectAtIndex:indexPath.row]origin];  cell.imageView.image  =  [[_itemsList  objectAtIndex:indexPath.row]image];  cell.descriptionLabel.text  =  [[_itemsList  objectAtIndex:indexPath.row]description];    return  cell;  

Le modifiche apportate al metodo sono sostanzialmente due: dichiarazione dell’oggetto cell come istanza della classe FruitCell ( e non più della classeUITableViewCell) e associazione della descrizione del frutto alla nuova labeldefinita nella classe FruitCell. Il risultato finale, effettuando un Run del progetto è il seguente:

 

Gestire la navigazione delle celle di una tabella

Il comportamento classico delle applicazioni iOS quando l’utente tocca una cella di una tabella è quello di caricare il contenuto associato in un nuovo View Controller che rimpiazza quello contenente la tabella per mezzo di un’animazione. Questo comportamento standard viene implementato per mezzo dell’oggetto UINavigationController (costituito da una barra che verrà mostrata nella parte alta dello schermo) che si occuperà della gestione dei contenuti (mostrare il dettaglio della cella selezionata e tornare al view controller contenente la tabella).

Page 62: Programmazione iOS

  62  

Per implementarlo abbiamo bisogno di aggiungere una linea di codice nel file AppDelegate.m dopo la dichiarazione dell’oggetto ListViewController:

UINavigationController  *  listNav  =  [[UINavigationController  alloc]initWithRootViewController:listViewController];  

Infine sostituiamo la linea dove aggiungevamo l’oggetto listViewControllerall’array controllers come segue:

[controllers  addObject:listNav];  

Adesso, effettuando un Run del progetto, vedremo la schermata relativa all’oggetto ListViewController come nel seguente screenshot:

Schermata relativa al ListViewController con il navigation controller

Come possiamo vedere è apparso il navigation controller che abbiamo inserito, nella parte alta dello schermo. Adesso dobbiamo prima di tutto creare un nuovoView Controller chiamato FruitDetailViewController e poi implementare, nel fileListViewController.m un nuovo metodo di delegato della tabella come segue (prima effettuiamo l’import della classse FruitDetailViewController):

-­‐  (void)tableView:(UITableView  *)tableView  didSelectRowAtIndexPath:(NSIndexPath  *)indexPath{          [tableView  deselectRowAtIndexPath:indexPath  animated:YES];          FruitDetailViewController  *  fruitDetail  =  [[FruitDetailViewController  alloc]init];          [self.navigationController  pushViewController:fruitDetail  animated:YES];  }  

Page 63: Programmazione iOS

  63  

Quello che facciamo all’interno del metodo è, nella prima linea, deselezionare la cella toccata dall’utente, creare un oggetto di tipo FruitDeatailViewController e poi richiamare il metodo pushViewController:  animated: per mostrare il nuovoViewController. Dato che le nostre celle, una volta selezionate, andranno a mostrare del contenuto aggiuntivo è necessario informare l’utente di questa funzionalità aggiungendo la seguente linea di codice nel metodo tableView:  cellForRowAtIndexPath: prima della linea di codice return  cell:

cell.accessoryType  =  UITableViewCellAccessoryDisclosureIndicator;  

Questa linea farà apparirà un triangolino nero alla destra di ogni cella che appunto indicherà l’utente la presenta di contenuto aggiuntivo. Effettaundo un Run del progetto e toccando una qualsiasi cella verrà mostrato il nuovo view controller:

Schermata relativa al FruitDetailViewController

Come possiamo vedere è apparso un bottone nella parte sinistra del navigation controller che rappresenta il backButton. Quest’ultimo, se toccato, mostrerà nuovamente il ListViewController con la tabella. La potenza dell’oggetto UINavigationController è proprio quella di gestire in totale autonomia la visualizzazione dei vari view controllers.

 

Page 64: Programmazione iOS

  64  

Raggruppare le celle di una tabella

Andiamo adesso a comporre l’ultima interfaccia del nostro progetto così suddivisa: nella parte alta un oggetto di tipo UIImageView per mostrare l’immagine associata al frutto e un oggetto di tipo UITableView con stile grouped.

Prima di tutto iniziamo ad apportare alcune modifiche al file FruitDetailViewController.h dichiarando i seguenti attributi:

Fruit  *  _fruit;  UITableView  *  _tableView;  

ed il seguente metodo di init:

-­‐  (id)initWithContentOfFruit:(Fruit*)fruit;  

Spostiamoci poi nel file FruitDetailViewController.m ed implementiamo il metodo di init precedentemente dichiarato:

-­‐  (id)initWithContentOfFruit:(Fruit  *)fruit{          self  =  [super  initWithNibName:nil  bundle:nil];          if  (self)  {                    _fruit  =  fruit;                  self.view.backgroundColor  =  [UIColor  underPageBackgroundColor];                    UIImageView  *  image  =  [[UIImageView  alloc]initWithImage:_fruit.image];                  image.frame  =  CGRectMake(70,  10,  180,  180);                  [self.view  addSubview:image];                    _tableView  =  [[UITableView  alloc]initWithFrame:CGRectMake(0,  200,  320,  236)  style:UITableViewStyleGrouped];                  _tableView.dataSource  =  self;                  _tableView.delegate  =  self;                  [self.view  addSubview:_tableView];                    self.hidesBottomBarWhenPushed  =  YES;            }          return  self;  }  

Ma perchè utilizzare l’oggetto UIImageView e non direttamente l’oggetto UIImagecome visto nelle precedenti lezioni? Perchè adesso, per la prima volta, abbiamo bisogno di attaccare un’immagine alla root view del view controller invocando il metodo addSubView:.

L’oggetto UIImage non deriva dalla classe UIView in quanto il suo compito è solo quello di immagazzinare i bytes relativi all’immagine e dunque non potrà essere passato come parametro al metodo addSubView: che appunto richiede un oggetto di tipo UIView. Per questo motivo è necessario utilizzare la classe UIImageView, che deriva da UIView, e che possiede al suo interno un’oggetto di tipo UIImage dove potremo caricare l’immagine vera e propria. Come ultima linea di codice, agiamo sulla property  hidesBottomBarWhenPushed che consente di nascondere la TabBar quando viene mostrato questo View Controller. Adesso, dopo aver dichiarato la tabella con stile grouped, andiamo ad implementare i metodi di data source come segue:

-­‐  (int)numberOfSectionsInTableView:(UITableView  *)tableView{          return  3;  }    -­‐  (int)tableView:(UITableView  *)tableView  numberOfRowsInSection:(NSInteger)section{          return  1;  }    -­‐  (UITableViewCell*)tableView:(UITableView  *)tableView  cellForRowAtIndexPath:(NSIndexPath  *)indexPath{          static  NSString  *CellIdentifier  =  @"Cell";            UITableViewCell  *cell  =  [tableView  

Page 65: Programmazione iOS

  65  

dequeueReusableCellWithIdentifier:CellIdentifier];          if  (cell  ==  nil)  {                  cell  =  [[UITableViewCell  alloc]  initWithStyle:UITableViewCellStyleDefault  reuseIdentifier:CellIdentifier];          }          if  (indexPath.section  ==  0)  {                  cell.textLabel.text  =  [NSString  stringWithFormat:@"Nome  :  %@",  _fruit.name];          }          if  (indexPath.section  ==  1)  {                  cell.textLabel.text  =  [NSString  stringWithFormat:@"Origine  :  %@",  _fruit.origin];          }          if  (indexPath.section  ==  2)  {                  cell.textLabel.text  =  [NSString  stringWithFormat:@"Descrizione  :  %@",  _fruit.description];          }          cell.selectionStyle  =  UITableViewCellSelectionStyleNone;          cell.textLabel.font  =  [UIFont  boldSystemFontOfSize:14];            return  cell;  }  

Come possiamo vedere dal codice decidiamo di dividere la tabella in sezioni formate da una unica riga. Nel metodo tableView:  cellForRowAtIndexPath: di popolamento quello che facciamo è istanziare una cella standard (questa volta con style UITableViewCellStyleDefault) e poi, controllando l’indice relativo alla sezione della tabella, popoliamo la cella opportunamente (viene effettuata una concatenazione di una stringa utilizzando il metodo di classe stringWithFormat:). Infine associamo alla property  selectionStyle il valore UITableViewCellSelectionStyleNone perché non vogliamo che la cella subisca una modifica grafica quando l’utente tocca la cella. L’interfaccia del FruitDetailViewController è terminata. Adesso torniamo nel fileListViewController.m e sostituiamo il contenuto del metodo tableView:  didSelectRowAtIndexPath: come segue:

[tableView  deselectRowAtIndexPath:indexPath  animated:YES];  FruitDetailViewController  *  fruitDetail  =  [[FruitDetailViewController  alloc]initWithContentOfFruit:[_itemsList  objectAtIndex:indexPath.row]];  fruitDetail.title  =  [[_itemsList  objectAtIndex:indexPath.row]  name];  [self.navigationController  pushViewController:fruitDetail  animated:YES];  

Effettuando un Run dell’applicazione potremo vedere lo stile grouped della tabella divisa in sezioni:

Schermata relativa al FruitDetailViewController con tabella con stile grouped  

Page 66: Programmazione iOS

  66  

Ridimensionare le celle in base al contenuto

Uno dei problemi che spesso uno sviluppatore si trova ad affrontare è quello di ridimensionare, in maniera automatica, l’altezza delle celle di una tabella in relazione al contenuto inserito. Se eseguiamo un Run del progetto ed inseriamo un testo abbastanza lungo nel campo descrizione nel modulo di inserimento vedremo che il contenuto non viene mostrato nella sua interezza:

Schermata della tabella senza ridimensionamento dell'altezza delle celle

Risolviamo questo problema. Prima di tutto, nel metodo tableView:  cellForRowAtIndexPath: della classe FruitDetailViewController.m aggiungiamo la seguente linea di codice dopo la definizione del font della cella:

cell.textLabel.numberOfLines  =  0;  

Di default, il numero di linee di una label è pari ad 1; inserendo il valore 0associamo alla label un valore di linee “infinite” che è proprio quello che ci serve, dato che a priori non sappiamo la lunghezza del testo da inserirvi. Detto questo, sempre nel file FruitDetailViewController.m aggiungiamo il seguente metodo di delegato:

Page 67: Programmazione iOS

  67  

-­‐  (CGFloat)tableView:(UITableView  *)tableView  heightForRowAtIndexPath:(NSIndexPath  *)indexPath{                

if  (indexPath.section  ==  2)  {              

     CGRect  frame  =  self.view.frame;              

     CGSize  maximumLabelSize  =  CGSizeMake(frame.size.width-­‐90,999);              

     NSString  *  string  =  [NSString  stringWithFormat:@"Descrizione:  %@",    

                                               _fruit.description  ];              

     CGSize  expectedLabelSize  =  [string    

                                                             sizeWithFont:[UIFont  boldSystemFontOfSize:14]    

                                                             constrainedToSize:maximumLabelSize    

                                                             lineBreakMode:UILineBreakModeWordWrap];                    

     return  expectedLabelSize.height;                                      

     if  (expectedLabelSize.height  <  40)  {  

             return  40;    

     }else{                            

           return  expectedLabelSize.height;                    

     }          

}  else{                  

       return  40;            

}    

}  

Il conteggio che andiamo a fare all’interno dell’if è semplice. Prima di tutto definiamo un size massimo nella variabile maximumLabelSize. Poi, dopo aver acquisito il testo da inserire nella variabile string richiamiamo il metodo sizeWithFont:  constrainedToSize:  lineBreakMode: (metodo della classe NSString) che ritorna un CGSize (ovvero un’altezza e una larghezza) che consentirà di mostrare correttamente il contenuto della variabile string. I parametri che passiamo a tale metodo sono: il tipo di font utilizzato nella label, il size massimo (definito nella variabile maximumLabelSize) e il modo con cui le linee di testo vengono interrotte quando raggiungono il bordo della label. Eseguendo un Run del progetto vedremo che la cella si adatterà, in altezza, per consentire la visualizzazione dell’intero testo:

Page 68: Programmazione iOS

  68  

Schermata della tabella con ridimensionamento dell'altezza delle celle  

Cancellare le celle di una tabella

Una delle operazioni più comuni che si possono effettuare su un’oggetto di tipo UITableView è la cancellazione di una cella. Ciò, solitamente, è consentito o premendo un bottone “Modifica” che abilita la cancellazione delle celle della tabella oppure effettuando uno slide direttamente sulla cella.

Andiamo dunque nel file ListViewController.m e all’interno dell’if del metodo initWithNibName:  bundle: inseriamo il seguente codice:

self.editButtonItem.title  =  @"Modifica";  self.navigationItem.rightBarButtonItem  =  self.editButtonItem;  

Quello che facciamo è semplicemente assegnare un titolo al bottone editButtonItem (che ereditiamo dalla derivazione dalla classe UIViewController) e lo posizioniamo come bottone destro della barra. Il risultato, effettuando un Run del progetto è il seguente:

Page 69: Programmazione iOS

  69  

Schermata relativa al ListViewController con il bottone per la cancellazione delle celle della tabella

Ovviamente, toccando il bottone Modifica non succederà niente perché abbiamo bisogno di definire i seguenti metodi sempre nel ListViewController.m:

-­‐  (void)setEditing:(BOOL)editing  animated:(BOOL)animated{          [super  setEditing:editing  animated:animated];                    [_tableView  setEditing:editing  animated:animated];                    if(editing  ==  YES){                  self.editButtonItem.title  =  @"Fine";          }          else{                  self.editButtonItem.title  =  @"Modifica";          }  }    -­‐  (BOOL)tableView:(UITableView  *)tableView  canEditRowAtIndexPath:(NSIndexPath  *)indexPath  {          return  YES;  }    -­‐  (void)tableView:(UITableView  *)tableView  commitEditingStyle:(UITableViewCellEditingStyle)editingStyle  forRowAtIndexPath:(NSIndexPath  *)indexPath  {          if  (editingStyle  ==  UITableViewCellEditingStyleDelete)  {                  [_itemsList  removeObjectAtIndex:indexPath.row];                  [_tableView  deleteRowsAtIndexPaths:[NSArray  arrayWithObject:indexPath]  withRowAnimation:UITableViewRowAnimationRight];          }            }  

Andiamo ad analizzare i metodi inseriti:

• setEditing:  animated: : è il primo metodo che viene richiamato dopo la pressione del tasto Modifica. Quello che facciamo al suo interno è richiamare il medesimo metodo sulla nostra tabella (facendola entrare quindi in uno stato di editing) e poi cambiare il titolo del bottone modifica in base al booleano editing.

Page 70: Programmazione iOS

  70  

• tableView:  canEditRowAtIndexPath: : in questo metodo è possibile decidere quale celle possono essere editabili e quali no. Nel nostro caso ritorniamo indistintamente YES consentendo la cancellazione di tutte le celle.

• tableView:  commitEditingStyle:  forRowAtIndexPath:: in questo metodo viene effettuata la cancellazione vera e propria. Come prima cosa ci assicuriamo che il valore di editingStyle siaUITableViewCellEditingStyleDelete (ovvero si stia cancellando una cella) e poi rimuoviamo dall’array _itemsList l’oggetto Fruit associato alla cella ed infine eliminiamo fisicamente la cella dalla tabella.

Effettuando un Run del progetto vedremo la funzionalità di cancellazione corretamente implementata, ma noteremo anche che la label descriptionLabel, definita nella classe FruitCell, non effettua un movimento verso destra come le altre label (la textLabel e la detailTextLabel).

Schermata relativa al problema del mancato spostamento di una label di una cella durante la cancellazione

Ciò avviene perché, per la nostra tabella, abbiamo utilizzato delle celle custom e nella nostra classe FruitCell non abbiamo implementato il metodo setEditing:  animated:.

Dunque per consentire lo spostamento della label andiamo ad inserire il seguente codice nel file FruitCell.m:

-­‐  (void)setEditing:(BOOL)editing  animated:(BOOL)animated{          [super  setEditing:editing  animated:animated];          if  (editing  ==  YES)  {              descriptionLabel.frame  =  CGRectMake(118,  35,  320,  50);          }          else{              descriptionLabel.frame  =  CGRectMake(78,  35,  320,  50);          }  }  

Page 71: Programmazione iOS

  71  

Schermata con tutte le labels della cella correttamente spostate

Come possiamo vedere lo spostamento è immediato. Si lascia per esercizio al lettore l’implementazione di un’animazione di traslazione come quella vista nella lezione riguardante lo spostamento della TextView.