Sie sind auf Seite 1von 254

0 Programmazione con Objective-C e Cocoa:

iOS, Mac, OSX, SDK, Tech Room

Linnovazione portata dalliPhone non solo legata allarrivo di un nuovo dispositivo HW (in fondo di telefoni e smartphone ce nerano anche prima) ma alla possibilit di creare e vendere delle applicazioni in maniera semplice come mai prima. Questo percorso cerca di fornire gli strumenti concettuali alla base della (per qualcuno arte) programmazione per iOS (iPhone e iPad). Non aspettatevi di imparare subito un linguaggio di programmazione, lo vedremo solo pi avanti; ma quando sar arrivato il momento spero che sar abbastanza semplice impararlo. Il percorso che seguiremo pu essere raggruppato in 4 fasi, anche se non sempre sar possibile separarle in maniera netta Introduzione alla programmazione Il modello Object Oriented Il linguaggio Objective C I fondamenti di Cocoa (il framework Apple per iOS) Non si tratta di un corso nel senso tradizionale del termine ma piuttosto di una serie di stimoli per una discussione a supporto della crescita di una comunit di sviluppatori

1 Introduzione alla Programmazione: Cosa la programmazione


Guide, iOS, Mac, OSX, SDK, Tech Room

Scrivere un programma come giocare con le costruzioni, necessario sapere cosa si vuole costruire, quali mattoncini si hanno a disposizione e come si possono collegare tra di essi. Cosa significa programmare? E inutile precisarlo? Forse; ma non costa nulla ed meglio chiarire i concetti base. Programmare significa dire, ad un dispositivo capace di eseguirle, quali azioni deve compiere. E una descrizione semplicistica? pu darsi ma quanto ci serve per il momento e ne rappresenta lessenza. Iniziamo subito con un programma memorizza lora di inizio della registrazione memorizza lora di fine della registrazione memorizza il canale da registrare leggi lora corrente lora fissata per linizio della registrazione? se non lo torna a 4

altrimenti seleziona il canale da registrare avvia la registrazione leggi lora corrente lora fissata per la fine della registrazione? se non lo torna a 11 altrimenti ferma la registrazione

Lo avete riconosciuto? Penso di si il programma eseguito dal vostro decoder, video-registratore o altro per registrare la vostra trasmissione preferita. In effetti un programma che potreste dare scritto su un foglietto ad un vostro amico con un orologio e chiedergli di eseguirlo se avete un registratore senza timer. Quali strutture logiche di base possiamo individuare in questo programma? Abbiamo delle azioni: memorizza lora memorizza il canale leggi lora corrente confronta due ore seleziona il canale avvia la registrazione ferma la registrazione abbiamo delle informazioni:

ora corrente ora di inizio registrazione canale da registrare ora di fine registrazione abbiamo un controllo della successione delle operazioni (flusso di elaborazione) le operazioni sono eseguite una dopo laltra (1,2,3,4,) al verificarsi o meno di determinate condizioni pu cambiare la sequenza di operazioni (se non ora di registrare torniamo a controllare lora altrimenti proseguiamo, dopo 7 torno a 4, dopo 5 possiamo andare a 9,) Lastrazione di ci che vogliamo che il nostro dispositivo faccia fondamentale ancora prima della conoscenza del linguaggio di programmazione. In sostanza dobbiamo prima sapere modellare il nostro pensiero (producendo quello che si chiama algoritmo) e poi cercare di spiegarlo con un linguaggio specializzato.

2 Introduzione alla Programmazione: Azioni e Informazioni


Guide, iOS, Mac, OSX, SDK, Tech Room

I mattoncini elementari che possiamo usare per la nostra costruzione sono le azioni e le informazioni messe a disposizione dallambiente nel quale vogliamo programmare. LE AZIONI. Riprendiamo le azioni individuate nel programma precedente memorizza lora

memorizza il canale leggi lora corrente confronta due ore seleziona il canale avvia la registrazione ferma la registrazione ed analizziamole per individuare i mattoncini che abbiamo usato. Che tipo di operazioni abbiamo fatto? Abbiamo ricevuto delle informazioni dallesterno (ora di inizio, ora di fine, canale), le abbiamo memorizzate, abbiamo fatto delle operazioni su di esse, in particolare le abbiamo confrontate, e abbiamo fatto delle operazioni di uscita, cio siamo intervenuti sul registratore (seleziona il canale, avvia la registrazione, ferma la registrazione). Quindi quali tipi di mattoncini abbiamo individuato? Operazioni di ingresso Operazioni di memorizzazione Operazioni sulle informazioni Operazioni di uscita

LE INFORMAZIONI. Ogni programma un minimo significativo lavora su informazioni, anzi se ci pensate un attimo si parla proprio di elaborazione delle informazioni, informatica, evidenziando il ruolo principe che hanno le informazioni quando si programma. Riprendiamo le informazioni del nostro esempio

ora corrente ora di inizio registrazione canale da registrare ora di fine registrazione Cosa sono? In pratica sono i valori sui quali lavora il nostro programma. La prima cosa da notare che quando impostiamo la nostra registrazione andiamo a riempire dei campi con le informazioni che vogliamoquindi possiamo dire che ogni informazione deve essere memorizzata per poter essere recuperata successivamente, quindi ogni informazione ha il suo nome con il quale viene richiamato il valore nel nostro programma (Es. nome: canale da registrare, valore: 523). Inoltre nel campo destinato allora non possiamo scrivere il canale da registrare, le due informazioni hanno strutture diverse ogni informazione ha un proprio insieme di valori (tipo di dato). Questi due aspetti nome dellinformazione tipo di dato sono alla base della gestione delle informazioni in un programma; e definiscono una variabile: la componente fondamentale della gestione dei dati in un programma.

TIPI DI DATO Riprendiamo ancora le informazioni del nostro esempio ora corrente

ora di inizio registrazione canale da registrare ora di fine registrazione Abbiamo alcune informazioni in forma di data e ora (corrente, di inizio registrazione, di stop) e una informazione in forma di numero (canale). Questa caratteristica delle informazioni, cio linsieme dei valori che pu assumere, indicato dal tipo di dato. I tipi di dato rispondono ad una macro classificazione. Tipi di dato scalari. Le informazioni viste finora contengono ognuna un solo valore (una data-ora, un numero di canale). Questi tipi di dato che contengono un solo valore sono chiamati scalari; esempi tipici di tipi scalari sono: numero intero (1,2,-3,287,) numero decimale (2.5, 98.435, 3.1415,) carattere (a, b, g,) stringa o sequenza di caratteri (questa una stringa, unaltra, c,) Ogni linguaggio mette a disposizione un insieme di tipi di dato scalare. Tipi di dato composti. Sono detti composti i tipi di dato che contengono pi valori. Introduciamoli con un esempio. Riprendiamo una parte del nostro programma leggi lora corrente lora fissata per linizio della registrazione? se non lo

torna a 1 altrimenti seleziona il canale da registrare avvia la registrazione leggi lora corrente lora fissata per la fine della registrazione? se non lo torna a 8 altrimenti ferma la registrazione Questo programma, gestisce una sola registrazione; come dovremmo fare per gestire pi impostazioni di registrazione in un elenco? leggi lora corrente per ogni impostazione nellelenco delle registrazioni se lora di inizio uguale allora corrente seleziona il canale da registrare avvia la registrazione per ogni registrazione in corso se lora di fine uguale allora corrente ferma la registrazione torna a 1 Ora quale nuovo tipo di informazioni abbiamo inserito? Lelenco delle registrazioni. E un tipo di dato particolare perch non contiene un solo valore ma una molteplicit di valori: tutte le registrazioni programmate. Questo tipo di dato composto che contiene dati omogenei (dello stesso tipo) detto elenco (nei linguaggio di programmazione array).

Un altro tipo di dato che possiamo individuare (anche se era presente anche prima ma ora pi evidente) la registrazione; un dato composto a sua volta da tre informazioni non omogenee: data di inizio canale da registrare data di fine Quindi i dati scalari (non omogenei a differenza degli elenchi) sono stati raggruppati in un altro tipo di dato, questo tipo di costruzione si chiama struttura. Un altro esempio di struttura lo possiamo vedere con la data (che pu essere interpretata a volte come scalare e a volte come composto), costituita da: anno mese giorno ora minuto Bene, riepilogando, la caratteristica tipo di dato delle informazioni uno degli aspetti fondamentali della gestione delle informazioni e pu essere analizzato rispetto alla sua molteplicit: tipo scalare tipo composto e per i tipi composti si individuano due famiglie principali elenchi: insiemi di informazioni omogenee, si usano per modellare le liste di informazioni (lelenco delle registrazioni); strutture: insiemi di dati non necessariamente omogenei, si usano per modellare le caratteristiche di una informazione (le informazioni che descrivono una registrazione)

Provate a riflettere un po su queste classificazioni ed ad assegnarle a concetti con i quali avete a che fare nella vostra vita quotidiana.

3 Introduzione alla Programmazione: I flussi di controllo


iOS, Mac, OSX, SDK, Tech Room

Riprendiamo il segmento di programma leggi lora corrente per ogni impostazione nellelenco delle registrazioni se lora di inizio uguale allora corrente seleziona il canale da registrare avvia la registrazione per ogni registrazione in corso se lora di fine uguale allora corrente ferma la registrazione torna a 1 Dal punto di vista della sequenza elaborativa, cio della sequenza

nella quale sono eseguite le operazioni cosa possiamo osservare? Innanzitutto abbiamo la successione: listruzione 2 segue la 1, lazione 5 segue la 4, Poi abbiamo lesecuzione condizionata: se lora di inizio(riga 3) allora esegui alcune cose altrimenti no, cio un blocco di azioni viene eseguito solo se si verifica una determinata condizione (riesci a trovare unaltra esecuzione condizionata in questo esempio?) Infine abbiamo il ciclo: per ogni impostazione(riga 2) esegui certe azioni, cio un gruppo di azioni eseguito ripetutamente su determinati elementi (riesci a trovare un altro ciclo in questo esempio?) Abbiamo cos individuato le principali strutture base relativamente al flusso di controllo la sequenza o successione lesecuzione condizionata il ciclo

BLOCCHI Riguardiamo il programma sopra e vediamo che c un insieme di istruzioni che possiamo raggruppare, quelle che vanno da 2 a 5; sono istruzioni che sono eseguite per ogni registrazione impostata nel nostro apparecchio. Ne possiamo poi individuare un altro (le righe 4 e 5) che sono eseguite ogni volta che necessario avviare una registrazione. Questa caratteristica di raggruppamento delle istruzioni la strutturazione a blocchi del nostro programma. Vediamo con un semplice esempio come si pu usare questa caratteristica considerando che ad un blocco possiamo dare un nome.

Scriviamo un blocco che chiamiamo controlla avvio se lora di inizio uguale allora corrente seleziona il canale da registrare avvia la registrazione con lausilio di questo blocco il nostro programma principale diventa leggi lora corrente per ogni impostazione nellelenco delle registrazioni controlla avvio della registrazione per ogni registrazione in corso se lora di fine uguale allora corrente ferma la registrazione torna a 1 Abbiamo ottenuto una maggiore leggibilit del nostro programma, inoltre abbiamo isolato un insieme di istruzioni che possiamo richiamare da diverse parti del programma pur avendole scritte una volta sola. Prova ad individuare altri blocchi e ad isolarli con un nome da usare allinterno del programma.

4 Introduzione alla Programmazione: Riepilogo

iOS, Mac, OSX, SDK, Tech Room

Rimettiamo in ordine le cose. Bene, abbiamo concluso la parte introduttiva sui concetti generali della programmazione. Pu sembrare una parte inutile e prolissa ma non cos. E importante che siano chiari i concetti generali che abbiamo visto e che sono alla base della scrittura dei programmi. Abbiamo tre costrutti di base Azioni Operazioni di ingresso Operazioni di memorizzazione Operazioni sulle informazioni Operazioni di uscita Informazioni Tipi di dato scalari Tipi di dato composti Flussi di controllo Sequenza Esecuzione condizionata Ciclo Blocchi Ora con questi costrutti puoi provare a scrivere dei programmi (come quello del decoder che abbiamo avuto come esempio) tratti dalla vita di tutti i giorni. Nelle prossime sezioni vedremo come

tradurli in istruzioni comprensibili ad un computer.

5 Il modello Object-Oriented: I fondamenti


iOS, Mac, OSX, SDK, Tech Room

Abbiamo visto che scrivere un programma consiste essenzialmente nellorganizzare il nostro pensiero (ed il procedimento che vogliamo eseguire) in maniera strutturata ed in termini di Informazioni, Azioni, Flusso. Ora per dobbiamo porci il problema di come esprimere queste cose in modo comprensibile al nostro amico computer. Se dovessimo dare le istruzioni ad un nostro amico inglese come faremmo? semplice le scriveremmo in inglese ( e se non conosciamo linglese? lo dovremo studiare); e se il nostro amico fosse francese? le scriveremmo in francese (e se non conosciamo il francese? lo dovremo studiare); e se il nostro amico fosse tedesco? Allora semplice basta sapere che lingua conosce il nostro computer, gi che lingua conosce il nostro computer? Impulsi elettrici presenti o assenti. Bhe un po troppo lontana dalla nostra comprensione per metterci a studiarla, per questo motivo sono stati definiti dei linguaggi (di programmazione) che sono abbastanza simili al linguaggio umano da renderne agevole lutilizzo. I diversi linguaggi consentono la modellazione del mondo secondo diversi approcci. Lapproccio che noi seguiremo quello chiamato Object-Oriented ed il linguaggio nel quale scriveremo i nostri

programmi sar Objective-C. LE CARATTERISTICHE DEL MODELLO OO Scrivere un programma secondo il modello Object-Oriented significa modellare il nostro pensiero secondo una struttura di oggetti cooperanti. Pu sembrare una cosa difficile ma se ci pensiamo un attimo tutta la nostra esperienza basata sugli oggetti, noi interagiamo continuamente con oggetti, la nostra auto un oggetto, la nostra tv un oggetto, il nostro iPhone un oggetto, e cos via

Cosa caratterizza un oggetto? Principalmente due cose ha uno stato interno, cio ha dei valori che lo caratterizzano: la nostra auto ha una cilindrata, un colore, un certo numero di km percorsi, un certo quantitativo di benzina nel serbatoio, ; la nostra tv ha una dimensione espressa in pollici, ha una serie di canali memorizzati, ha un canale selezionato per la visualizzazione, ha un livello di volume, sa fare alcune cose, cio in grado di eseguire delle azioni in risposta a stimoli esterni: la nostra tv sa cambiare canale, sa alzare ed abbassare il volume, Questi due concetti (lo stato interno e gli stimoli cui in grado di rispondere un oggetto) sono le caratteristiche fondamentali della programmazione OO. Riflettiamo un attimo su una cosa (prendendo come esempio il televisore): in effetti tutti i televisori avranno una dimensione, una

serie di canali memorizzati, un canale selezionato, un certo livello di volume, e cos via e tutti sapranno cambiare canale, alzare ed abbassare il volume. Possiamo quindi dire che c una caratterizzazione comune a tutti i televisori, anche se poi ogni singolo televisore avr valori diversi per il proprio stato (il televisore nel soggiorno avr una lista di canali e quello in camera potr averne una diversa, in soggiorno guardo un canale mentre in camera ce n un altro, in soggiorno ascolto con un certo volume e in camera con un altro,). Le caratteristiche comuni a tutti i televisori determinano quella che si chiama Classe, mentre ogni singolo televisore si chiama Istanza (o Oggetto). Nel nostro mondo abbiamo quindi la Classe Televisore che dice che ogni Oggetto televisore avr una lista dei canali, un canale selezionato, un livello per il volume, e sapr cambiare canale e alzare ed abbassare il volume e poi avremo tante Istanze di Televisore ognuna con i propri valori per le caratteristiche definite dalla Classe. E importante capire la differenza tra la classe Televisore (esiste una sola classe Televisore) e le sue istanze (ogni singolo televisore esistente).

Riprendiamo adesso in esame il nostro televisore per vedere quali altre caratteristiche ha. Sicuramente ha un consumo espresso in KwH, ma anche il nostro frigorifero ha un consumo e anche il nostro forno a microonde, e cos via. Possiamo dire che qualunque elettrodomestico ha un

consumo, quindi diciamo che un televisore un elettrodomestico ed in quanto tale ha un consumo. Continuando lanalisi vediamo che il televisore (come qualunque elettrodomestico) ha un peso, ma anche la nostra sedia ha un peso pur non essendo un elettrodomestico (a meno che non siamo nel braccio della morte); in realt qualunque oggetto fisico (sulla terra ) ha un peso, abbiamo quindi unaltra caratteristica che arriva ad ogni elettrodomestico in quanto oggetto fisico. Bene, che conclusioni possiamo trarre da questa analisi: la prima che le classi che descrivono gli oggetti si possono strutturare in gerarchie.

Per descrivere una gerarchia si usano i termini superclasse/sottoclasse: un televisore un elettrodomestico che un oggetto fisico; elettrodomestico superclasse di televisore e sottoclasse di oggetto fisico, televisore sottoclasse di elettrodomestico e quindi anche di oggetto fisico; la seconda che le caratteristiche di una superclasse sono ereditate dalle sottoclassi: un televisore, oltre ad avere dei canali da mostrare ha un consumo in quanto elettrodomestico ed un peso in quanto oggetto fisico;

Vediamo un altro aspetto. Quando vogliamo cambiare il canale che stiamo guardando come facciamo? Apriamo il televisore e mettiamo le mani sulle componenti elettroniche fino a quando non riusciamo a vedere il canale desiderato? Immagino di no, anzi vi sconsiglio vivamente dal provarci. Perch non possiamo farlo? Perch in realt noi non sappiamo come il televisore faccia per cambiare la frequenza del sintonizzatore (inoltre ogni costruttore di televisori avr un suo proprio circuito per farlo); quello che possiamo fare usare la funzionalit che il tuo televisore ci mette a disposizione per cambiare canale, poi sa lui come aggiornare il suo stato interno. Lo stesso ragionamento si applica alla modifica del volume, anche qui potremo solo usare le due funzioni per alzare ed abbassare il volume e non potremo agire direttamente sullo stato elettronico del televisore. Questa caratteristica della modellazione ad oggetti si chiama incapsulamento: impone che dallesterno si possa agire sullo stato interno di un oggetto solo attraverso le funzionalit che espone. Solo loggetto stesso pu accedere alla sua struttura interna per ottenere leffetto voluto: noi possiamo solo chiedere al televisore di passare dal canale 1 al canale 2; lui invece sa come rispondere alla richiesta aggiornando la frequenza del sintonizzatore per vedere il nuovo canale. Dalle prossime lezioni inizieremo a vedere come si implementano nel linguaggio di programmazione Objective-C i concetti visti finora.

6 Iniziamo con XCode


iOS, Mac, OSX, SDK, Tech Room

A questo punto direi che il momento di cominciare a vedere un po di programmazione sul computer. Iniziando quindi a vedere un linguaggio di programmazione (Objective-C) e quali sono gli strumenti che si usano per scrivere un programma. La prima cosa di cui abbiamo bisogno un ambiente nel quale digitare il programma, questa componente si chiama Editor, la parte che in maniera pi o meno evoluta ci aiuta scrivere il codice del nostro programma ad esempio pu supportare in maniera automatica lindentatura delle righe del codice per una maggiore leggibilit o levidenziazione di parti del programma con colori diversi per mostrarne il tipo. Abbiamo detto che il nostro computer riconosce solo la presenza o assenza di corrente mentre noi useremo un linguaggio molto vicino a quello umano per scrivere il programma nellEditor; come colmiamo la distanza tra il linguaggio del computer e quello di programmazione? Ci pensa un altro componente che prende il programma scritto nellEditor e lo traduce in una forma comprensibile al computer; questo componente si chiama Compilatore/Linker chiamato di solito solo Compilatore.

Questi 2 componenti (Editor e Compilatore) oltre a tanti altri che, in parte, vedremo in seguito fanno parte di XCode: lambiente di sviluppo integrato per OSX. Durante queste lezioni noi useremo XCode 4. Bene ora lanciamo XCode, nella pagina di benvenuto scegliamo Cancel.

Ora dovremo creare un nuovo progetto XCode il progetto il contenitore del tuo programma per XCode. Andiamo nel Menu File>New>New Project e ci saranno proposti i template (modelli) di progetto gi predisposti da Apple. Questo primo esempio sar un progetto introduttivo sui concetti base che abbiamo visto e quindi non far uso dellinterfaccia utente delliPhone (ancora un attimo di pazienza) quindi scegliamo il template Command Line Tool che troviamo nel gruppo Application della sezione Mac OS X.

Nella finestra successiva (Invio o Next) diamo un nome al progetto, ad esempio PrimoProgetto, e nel menu a tendina lasciamo selezionata la voce Foundation; infine scegliamo la directory dove salvare il progetto. A questo punto siamo dentro XCode con il template del progetto predisposto, in realt si tratta di un progetto gi eseguibile ma per il momento non lo eseguiamo. Analizziamo invece cosa c.

Nel frame di sinistra selezionando la vista Gruppi e Files (la prima icona nella barra di controllo) possiamo vedere i file che fanno parte del progetto. Selezioniamo il file main.m ed analizziamone il contenuto. Un programma costituito di istruzioni; in un programma Objective-C possiamo avere 3 tipi di istruzioni: le istruzioni con la logica del nostro programma (quello che vogliamo che il programma faccia); se usiamo la metafora della traduzione il testo che vogliamo tradurre; le istruzioni che diamo al compilatore (direttive), si riconoscono perch iniziano con #, sono istruzioni che non servono alla logica del programma ma sono indicazioni che diamo al compilatore; nella metafora come se dopo aver dato il foglietto con il testo da tradurre al nostro amico traduttore gli dicessimo di usare la penna rossa per i verbi e quella blu per i nomi; i commenti, si riconoscono perch iniziano con // o con /* se si sviluppano su pi righe nel qual caso sono chiusi da */; servono ad aiutare chi dovr rileggere il nostro foglietto e capire perch abbiamo scritto certe cose ad esempio potremmo scrivere allinizio del nostro foglietto Questo un brano dallatto 2 dellAmleto Riesci a riconoscere a quale tipo di istruzione appartiene ognuna di quelle del file main.m? Ricordiamo che nel capitolo 6 abbiamo parlato dei blocchi a proposito dei flussi di controllo. Bene in Objective-C i blocchi di istruzioni sono racchiusi tra parentesi graffe { }, riesci ad individuare un blocco di istruzioni? Un particolare tipo di blocchi di istruzioni chiamato funzione ed

ha la caratteristica di dare un nome al blocco in modo da poterlo richiamare da altre parti del tuo programma. La sintassi della funzione tipoRisultato nomeFunzione() { } Man mano che ne avremo bisogno analizzeremo le varie parti della struttura di una funzione. Per ora importante capire che quella scritta significa che lesecuzione delle istruzioni racchiuse tra le { } pu essere richiesta dallinterno del tuo programma semplicemente scrivendo il nome della funzione, comunque lo vedremo meglio in seguito. Riesci a trovare una funzione, nel file main.m? In realt lunica funzione che trovi (si chiama main) il punto di partenza di ogni programma Objective-C; in ogni programma Objective-C deve esserci una funzione main ed quella che sar eseguita quando lanci la tua applicazione. Bene quindi, le istruzioni racchiuse tra le { } costituiscono in questo momento il tuo programma. La prima cosa che puoi notare che tutte le istruzioni di logica, quindi esclusi i commenti e le direttive (vedi sopra) terminano con il ;. Ricordi che a proposito del flusso di controllo avevamo parlato della sequenza? Bene in Objective-C la sequenza di istruzioni segnalata dal ; stiamo cio dicendo che finisce unistruzione e si passa alla successiva. Ora, per concludere questa sessione, prova ad eseguire il programma cliccando sul bottone Run.

Si aprir un pannello inferiore nella finestra di XCode e avrai una scritta tipo quella evidenziata in figura. Cio oltre ad una serie di informazioni di controllo che ignoriamo, il tuo programma scrive Hello, World!

Se torni a guardare nel codice vedrai che Hello, World! scritto allinterno dellistruzione NSLog(). Bene ora prova a cancellare Hello, World! e a scriverci Ciao, ! al posto dei puntini scrivici il tuo nome, ed esegui di nuovo il programma. A questo punto il programma dovrebbe salutare solo te. Abbiamo quindi trovato la prima istruzione di Objective-C che quella che consente al programma di scrivere qualcosa e quindi di comunicare con lesterno; ricordi la sessione sulla Azioni? Bene abbiamo trovato la prima delle azioni di uscita. Per ora basta cos, dalla prossima sessione cominceremo a modificare questo template per fare un piccolo programma che ci consenta, passo passo di vedere alcuni aspetti di Objective-C.

7 Creiamo le nostre classi


iOS, Mac, OSX, SDK, Tech Room

Ora cominciamo a far crescere il nostro programma. Prima di scrivere del codice necessario sapere cosa si vuole fare: noi scriveremo un programma che realizzi il modello semplificato di televisore che abbiamo visto nel capitolo 5. Quindi cosa vogliamo che sia gestito nel nostro programma? Oggetto fisico Caratteristiche: Peso (espresso in Kg): valore numerico decimale Elettrodomestico ( un oggetto fisico) Caratteristiche Consumo (espresso in W/h): valore numerico intero Televisore ( un elettrodomestico) Caratteristiche canali memorizzati: elenco di nomi di canali canale sintonizzato: numero intero che indica la

posizione del canale sintonizzato nellelenco dei memorizzati livello del volume: numero intero da 0 a 100 Funzionalit (cosa sa fare il nostro televisore) ricerca canali: valorizza lelenco dei canali memorizzati mostra elenco: mostra i nomi dei canali memorizzati mostra il canale: scrive il nome del canale selezionato alza il volume: aumenta di 1 il livello del volume abbassa il volume: decrementa di 1 il livello del volume vai al canale x : sintonizza il televisore sul canale x Tutto chiaro? E inutile andare a vedere come si fanno le cose in Objective-C se non sono chiare nella nostra mente. Bene ora andiamo in XCode e creiamo le 3 classi e la loro gerarchia. Fai click con il bottone destro sulla cartella con il nome del progetto, scegli New File e scegli Objective-C class tra i template che trovi alla voce Cocoa

Nella finestra successiva ti viene proposta la superclasse NSObject .

Si tratta di una classe particolare. E la classe fornita dal sistema come madre di tutte le classi (ci sono delle eccezioni ma si tratta di casi talmente particolari che per il momento li ignoriamo). In pratica la radice di tutte le gerarchie, quindi tutte le classi sono direttamente o indirettamente sottoclassi di NSObject ereditandone (ricorda cosa abbiamo detto nel cap.5) quindi tutte le caratteristiche e le funzionalit. Nella finestra successiva dobbiamo dare un nome alla nostra classe, scriviamo OggettoFisico nel campo SaveAs. Bene, cosa abbiamo ora in XCode? Abbiamo 2 nuovi file OggettoFisico.h e OggettoFisico.m. Generalizzando possiamo dire che ogni classe in Objective-C viene codificata attraverso due file il nomeClasse.h (chiamato header) e nomeClasse.m (chiamato implementation); nello header si codifica la struttura e cosa fa la nostra classe mentre nellimplementation si codifica come lo fa. Selezioniamo il file OggettoFisico.h. e analizziamone il contenuto.

La riga @interface OggettoFisico : NSObject dichiara al sistema la Classe dicendo qual il suo nome (OggettoFisico) e qual la sua superclasse (NSObject) in quanto tutte le classi in Objective-C devono avere una superclasse. Capiremo meglio le diverse parti di una Classe man mano che le useremo. Ora, in maniera analoga a quanto fatto per OggettoFisico, creiamo le altre due classi Elettrodomestico e Televisore, sempre come sottoclassi di NSObject.

Abbiamo quindi creato le nostre tre classi (OggettoFisico, Elettrodomestico e Televisore) ma per il momento sono tutte e tre direttamente sottoclassi di NSObject e non c alcuna relazione gerarchica tra di esse mentre noi vogliamo che Televisore sia sottoclasse di Elettrodomestico, a sua volta sottoclasse di OggettoFisico. Procediamo quindi alla creazione della gerarchia. Iniziamo con il definire la relazione tra Elettrodomestico e OggettoFisico. Selezioniamo Elettrodomestico.h e sostituiamo NSObject con OggettoFisico, dicendo in tal modo che Elettrodomestico sottoclasse di OggettoFisico. Come possiamo vedere XCode segnala un errore in quella riga. Questo perch il compilatore, allinterno del file Elettrodomestico.h. non sa chi sia OggettoFisico. Allora facciamoglielo conoscere aggiungendo la riga #import OggettoFisico.h prima della riga @interface Listruzione che abbiamo appena inserito una direttiva al

compilatore (inizia con #) che rende visibile la classe OggettoFisico. Osservando meglio il file ora possiamo notare che cera gi un #import e lo stesso allinizio del file main.m, la struttura leggermente diversa ma lo scopo lo stesso; rendere visibili, allinterno dei programmi che scriveremo, tutta una serie di elementi che sono stati predisposti da Apple e che fanno parte del linguaggio Objective-C. In maniera analoga creiamo la relazione tra Televisore ed Elettrodomestico, aprendo il file Televisore.h e sostituendo NSObject con Elettrodomestico. Anche in questo caso per evitare lerrore e rendere visibile la classe Elettrodomestico dobbiamo inserire la direttiva #import Elettrodomestico.h A questo punto abbiamo creato le tre classi e la gerarchia tra di esse. Nelle prossime lezioni diremo come sono fatte queste classi, in termini di caratteristiche e funzionalit e creeremo alcuni oggetti.

8 Definiamo le Classi
iOS, Mac, OSX, SDK, Tech Room

Nella lezione precedente abbiamo creato le tre classi (OggettoFisico, Elettrodomestico e Televisore) e messe in relazione gerarchica, ma non abbiamo ancora detto come sono fatte queste classi in termini di stato interno e di funzionalit ed quello che faremo ora; per prima di andare avanti cominciamo con il dare il nome corretto alle cose. Lo stato interno, come abbiamo detto nel cap 5, costituito dalle

informazioni gestite delloggetto (lista dei canali, canale selezionato, livello del volume,) e, come abbiamo detto nel cap. 2, le informazioni sono modellate attraverso le variabili, allinterno di una classe si indicano con il termine di variabile di istanza e come tutte le variabili sono descritte da un nome ed un tipo di dato. Le funzionalit sono definite attraverso blocchi di codice cui assegnato un nome e sono indicati con il nome di metodo. Lesecuzione di un metodo (cio del blocco di istruzioni associate a quel nome) richiesta con la sintassi [oggetto nomeMetodo] lo vedremo molte volte in seguito e avremo modo di approfondirne luso visto che listruzione base della programmazione Object-Oriented. Bene ora che abbiamo introdotto i termini corretti riprendiamo il nostro programma per definire le variabili di istanza ed i metodi delle nostre tre classi. Apriamo il nostro progetto in XCode. La prima cosa che faremo completare la definizione della classe OggettoFisico, ricordiamo che quello che vogliamo Oggetto fisico Caratteristiche Peso (espresso in Kg): valore numerico decimale Cio vogliamo che un oggetto di questa classe sia caratterizzato da un Peso, per fare ci dobbiamo dichiarare allinterno della nostra classe una variabile di istanza di tipo numerico. Selezioniamo il file OggettoFisico.h e allinterno delle parentesi {}, dopo la riga @private scriviamo

NSDecimalNumber *peso; in questo modo dichiariamo una variabile di istanza di nome peso e di tipo NSDecimalNumber, questo tipo definito tramite una classe Objective-C e si usa per modellare valori numerici, fra un attimo vedremo come si usa appena dovremo lavorare con in valori della variabile peso. Nella dichiarazione di questa variabile osserva bene la presenza di un *, come vedremo innumerevoli volte necessario usarlo sempre nella dichiarazione di variabili riferite a Classi. Non dimentichiamo di scrivere ; alla fine della riga per dire che finita unistruzione. Passiamo ora a codificare Elettrodomestico ( un oggetto fisico) Caratteristiche Consumo (espresso in W/h): valore numerico intero Quindi apriamo il file Elettrodomestico.h e allinterno delle {}, dopo la riga @private scriviamo NSDecimalNumber *consumo; in questo modo dichiariamo una variabile di istanza di nome consumo e di tipo NSDecimalNumber. Osserviamo che anche qui sono presenti il carattere * prima del nome della variabile ed il ; alla fine della riga. Infine passiamo ora a codificare Televisore ( un elettrodomestico) Caratteristiche canali memorizzati: elenco di nomi di canali canale sintonizzato: numero intero che indica la

posizione del canale sintonizzato nellelenco dei memorizzati livello del volume: numero intero da 0 a 100 Funzionalit (cosa sa fare il nostro televisore) ricerca canali: valorizza lelenco dei canali memorizzati mostra elenco: mostra i nomi dei canali memorizzati mostra il canale: scrive il nome del canale selezionato alza il volume: aumenta di 1 il livello del volume abbassa il volume: decrementa di 1 il livello del volume vai al canale x : sintonizza il televisore sul canale x quindi apriamo il file Televisore.h e allinterno delle {}, dopo la riga @private scriviamo NSArray *canaliMemorizzati; NSDecimalNumber *canaleSintonizzato; NSDecimalNumber *livelloVolume; rispetto a quanto visto finora abbiamo introdotto un nuovo termine del linguaggio: NSArray. Anche questa una classe di ObjectiveC, ed la classe usata per modellare un elenco di elementi referenziabili per posizione: lelemento in posizione 1, quello in posizione 2, quello in posizione 3, Conosceremo meglio anche questa classe man mano che la useremo nel nostro programma. Anche qui non mancano* e ;. Rispetto alle classi OggettoFisico e Elettrodomestico, televisore ha

anche delle funzionalit. Vediamo come si codificano. Per ogni funzionalit dovremo implementare un metodo. Quindi apriamo il file Televisore.h e dopo la } e prima della riga @end scriviamo -(void)ricercaCanali; -(void)mostraElenco; -(void)mostraIlCanale; -(void)alzaIlVolume; -(void)abbassaIlVolume; -(void)vaiAlCanale:(NSDecimalNumber *)canaleDaSintonizzare; Cosa sono queste istruzioni? Sono dichiarazioni di metodi, cio stiamo dicendo al sistema che gli oggetti della classe Televisore sanno rispondere a queste richieste.

Analizziamo un po la struttura sintattica di queste dichiarazioni. Iniziano tutte con un segno -, questo segno indica che si tratta di metodi di istanza, cio sono eseguiti da unistanza della classe ed hanno quindi accesso alle variabili di istanza; sono invocati con [oggetto nomeMetodo] se un metodo invece inizia con il segno + un metodo di classe cio eseguito da una classe e non da unistanza viene quindi invocato con [classe nomeMetodo] essendo eseguiti da una classe questi metodi non hanno accesso alle variabili di istanza; i metodi di classe sono solitamente usati

per creare istanze della classe. Dopo il segno - deve esser indicato il tipo del risultato restituito dal metodo. Cosa vuol dire che il metodo restituisce un valore? Il concetto di valore restituito si pu esemplificare facendo riferimento alle classiche operazioni matematiche: 1+1 restituisce il valore 2 cio una lesecuzione di una certa parte del nostro programma restituisce un valore a chi lha invocata. Quindi se avessimo un metodo che fa la somma scriveremmo -(NSDecimalNumber *)somma per dire che il metodo somma restituisce un oggetto che rappresenta un numero. Pi avanti nel nostro esempio definiremo anche dei metodi che restituiscono dei valori e capiremo meglio questo concetto. Per ora i nostri metodi non restituiscono nulla, ma intervengono direttamente sul televisore per cambiare canale o il volume e quindi si indica void per dire che non c risultato. Infine, dopo il tipo del risultato, abbiamo il nome del metodo. Se osserviamo lultimo metodo, per, vediamo che c ancora qualcosa -(void)vaiAlCanale:(NSDecimalNumber *)canaleDaSintonizzare; Questo metodo ha bisogno di uninformazione per poter essere eseguito. Se il metodo deve cambiare canale deve sapere a che canale andare e quindi questa informazione la riceve dallesterno e la chiama canaleDaSintonizzare. Un altro esempio: il nostro metodo somma non pu fare nulla se non riceve i due operandi da sommare; avremo quindi qualcosa del genere -(NSDecimalNumber *)somma:(NSDecimalNumber *)operando1

allOperando:(NSDecimalNumber *)operando2 Bene, abbiamo finito con le dichiarazioni che descrivono il nostro mondo di Televisori.

Nella prossima lezione definiremo i metodi, cio scriveremo cosa devono fare ed inizieremo a far funzionare qualche istanza di Televisore. 9 Implementiamo i metodi
iOS, Mac, OSX, SDK, Tech Room

Nella scorsa lezione abbiamo dichiarato quali sono i metodi della classe Televisore, ora inizieremo a scrivere cosa vogliamo che questi metodi facciano. Ovviamente non aspettatevi di vedere la tv sul vostro Mac. Apriamo il progetto e selezioniamo il file Televisore.m. In questo

file dovr essere scritto il codice per i metodi e come possiamo vedere ci sono delle cose gi predisposte da Apple, le analizzeremo fra un po. Ora dopo la } del metodo init inseriamo, per ogni metodo che abbiamo dichiarato in Televisore.h, lintestazione del metodo (la stessa che c nel .h) e una coppia di { } , dobbiamo per avere laccortezza di togliere il ; dallintestazione, perch in questo file la sola intestazione non unistruzione.

Bene, in questo modo abbiamo preparato lo spazio per le istruzioni dei diversi blocchi che costituiscono i nostri metodi. Scriviamo ora una prima versione della nostra applicazione che ci faccia vedere come unoggetto della classe Televisore risponde alle nostre richieste; per rendere il pi semplice possibile questa prima versione non faremo altro che far scrivere a console un messaggio di risposta alle nostre diverse richieste. Allinterno delle parentesi {} di ognuno dei nostri metodi scriviamo

NSLog (@Sto eseguendo il metodo ); mettendo il nome del metodo al posto dei Ricordiamo che listruzione NSLog labbiamo gi incontrata nel main.m ed listruzione che scrive sulla console il messaggio che passiamo tra parentesi, quindi quello che otterremo che allesecuzione di un metodo sar scritto a console il messaggio relativo. Inoltre allinterno del metodo init della classe Televisore, dopo la { scriviamo NSLog (@Sto inizializzando il televisore); allinterno del metodo init della classe Elettrodomestico, dopo la { scriviamo NSLog (@Sto inizializzando lelettrodomestico); allinterno del metodo init della classe OggettoFisico, dopo la { scriviamo NSLog (@Sto inizializzando loggetto fisico); Fra un attimo vedremo leffetto di queste ultime tre istruzioni. Bene, diciamo che per questa prima versione siamo soddisfatti della modellazione delle nostre classi: abbiamo definito la classe Televisore e le sue superclassi, abbiamo indicato le caratteristiche (variabili di istanza) e le funzionalit (metodi) che per ora si limitano a scrivere un messaggio a console.

Andiamo ad eseguire il nostro programma cliccando sul bottone Run ed otteniamoancora Hello, World! oppure Ciao,! con il tuo nome se hai fatto le modifiche indicate nella lezione 6. Tutta questa fatica per definire la classe Televisore e non cambiato nulla. Perch? Perch, come abbiamo detto il punto di ingresso del nostro programma sempre la funzione main che rimasta quella di prima, cio quella che fa solo NSLog( Dobbiamo quindi intervenire sul codice interno alla funzione main. Quindi selezioniamo il file main.m e cancelliamo listruzione NSLog( La prima cosa da fare creare un oggetto televisore, per ora abbiamo solo creato la classe, non loggetto, cio abbiamo detto come fato un televisore ma non ne abbiamo ancora nessuno in casa. Il televisore uninformazione del nostro programma e le

informazioni abbiamo detto che si modellano con le variabili, abbiamo quindi bisogno di una variabile che si riferisca al nostro televisore. Quindi allinterno della funzione main, dopo la prima { che inizia il blocco principale, dichiariamo la nostra variabile: Televisore *tv1; abbiamo dichiarato una variabile di nome tv1 e di tipo Televisore, cio la variabile tv1 si riferir ad istanze della classe Televisore, osserviamo come al solito il simbolo * trattandosi di un oggetto. A questo punto il compilatore ci segnala un errore che ci dice che non conosce Televisore, ricordiamo che lo stesso errore lo abbiamo ottenuto quando abbiamo cercato di dichiarare le superclassi di Elettrodomestico e Televisore prima di fare limport. Anche in questo caso stiamo dicendo al compilatore che useremo una variabile di tipo Televisore ma non gli abbiamo detto cosa Televisore. Quindi dobbiamo fare limport. Sotto la riga #import <Foundation/Foundation.h> scriviamo #import "Televisore.h" in questo modo il compilatore conosce la classe Televisore allinterno della funzione main e la pu usare. Bene, con la dichiarazione Televisore *tv1; abbiamo dato un nome alla variabile ma non abbiamo ancora creato loggetto lo facciamo adesso scrivendo, dove prima cera scritto NSLog(@Hello, World!) // insert code here... tv1=[[Televisore alloc] init]; analizziamo questa istruzione. Si tratta di una istruzione di assegnamento cio si assegna un valore ad una variabile, loperatore di assegnamento in Objective-

C =. La nostra variabile tv1, quindi dopo lesecuzione di questa istruzione tv1 conterr il valore risultato dalla valutazione di quello che c scritto a destra del simbolo =. Vediamo allora cosa c scritto a destra del simbolo =. Iniziamo con la parte pi interna [Televisore alloc] Se ricordiamo la struttura sintattica per linvocazione di un metodo di classe [classe nomeMetodo] vediamo che ci troviamo proprio in questa situazione, Televisore la nostra classe e alloc un metodo di classe predefinito da Objective-C nella classe NSObject e quindi ereditato da tutte le sue sottoclassi il cui obiettivo quello di costruire loggetto della classe su cui invocato e restituisce loggetto creato, cio unistanza di Televisore. Approfondimento: cerca NSObject nella documentazione Apple ed individua il metodo alloc. A questo punto lespressione completa [[Televisore alloc] init] la possiamo leggere come se fosse [istanzadiTelevisore init] Se ricordiamo la struttura sintattica per linvocazione di un metodo di istanza [istanza nomeMetodo] vediamo che ci troviamo proprio in questa situazione. Inoltre il metodo init proprio quello che noi troviamo nellimplementazione della classe Televisore (Televisore.m). Il metodo init quello che viene di solito usato per assegnare i valori iniziali alle variabili di istanza degli oggetti appena creati e restituisce loggetto stesso. Riassumendo tv1=[[Televisore alloc] init]; crea unistanza di Televisore, su questa istanza invoca (richiede lesecuzione di) il metodo init ed assegna loggetto creato ed inizializzato alla variabile tv1.

Bene fermiamoci un attimo ed eseguiamo il programma cliccando sul bottone Run.

Abbiamo alcuni messaggi a console. Guardiamo il primo Sto inizializzando il televisore Questo il messaggio del NSLog del metodo init della classe Televisore, ed in effetti quello che ci saremmo dovuti aspettare di ottenere visto che [[Televisore alloc] init] invoca proprio il metodo init della classe Televisore. Ma perch c anche Sto inizializzando lelettrodomestico il messaggio del metodo init della classe Elettrodomestico? Andiamo a guardare come fatto il metodo init della classe Televisore, nel file Televisore.m. Guardiamo listruzione self = [super init]; simile alla nostra tv1=, cio un assegnamento che assegna un valore alla variabile self. Self una variabile predefinita in

Objective-C che si riferisce sempre allistanza corrente che sta eseguendo listruzione nella quale scritto self. Ma che valore assegnamo a self? Il risultato di [super init]. Questa forma sintattica dovrebbe inziare ad esserci familiare linvocazione di un metodo di istanza (init) su un oggetto (super). Ma super chi ? Anche super una variabile predefinita di Objective-C ed anche super, come self, si riferisce sempre allistanza che esegue listruzione nella quale scritto super. Allora che differenza c con self? Quando si usa self loggetto viene visto come istanza della classe nella quale stato invocato (nel nostro caso Televisore), mentre quando si usa super loggetto viene visto come istanza della superclasse della classe nella quale stato invocato (nel nostro caso Elettrodomestico). Facciamo un esempio; se considero il televisore che ho nel soggiorno, quando ne cambio il canale o alzo e abbasso il volume lo sto usando in quanto televisore, uso le funzioni proprie di un televisore; se lo collego alla corrente, lo accendo, lo spengo, lo sto usando in quanto elettrodomestico, uso le funzioni proprie di un elettrodomestico; ma loggetto sempre lo stesso: il televisore del soggiorno. Nel primo caso mi riferir con self e nel secondo caso con super. Fra un po faremo un esempio specifico di questo aspetto che dovrebbe chiarirlo meglio. Tornando al nostro metodo init della classe Televisore quindi self = [super init]; potremmo leggerlo come inizializza un elettrodomestico ed assegnalo allistanza del televisore. In conclusione [super init] invoca il metod init della classe Elettrodomestico ed ecco spiegato perch a console c anche il messaggio

Sto inizializzando lelettrodomestico ma perch c anche Sto inizializzando loggetto fisico che il messaggio dellinit di OggettoFisico? Se guardiamo il metodo init di Elettrodomestico vediamo che anche qui c self = [super init]; e si applica quindi tutto il discorso precedente dove per super adesso fa riferimento alloggetto visto nella classe OggettoFisico. Per, siccome ci piace la precisione, sembrerebbe pi corretto leggere prima linizializzazione delloggetto fisico, poi quella dellelettrodomestico e poi quella del televisore, tanto per percorrere la gerarchia dalla classe pi generale a quella pi particolare; spostiamo i 3 NSLog che abbiamo inserito prima nei metodo init, dopo la riga self = [super init]; di ognuno dei metodi, in tal modo prima inizializziamo loggetto come istanza della superclasse e poi scriviamo il messaggio di inizializzazione della classe corrente. Analizziamo un ultimo aspetto. Stiamo usando il metodo init sempre in espressioni a destra di unistruzione di assegnamento (=), questo vuol dire che il metodo deve restituire un valore che sar assegnato alla variabile a sinistra del segno =. Ma quale valore restituisce il metodo init? La risposta nellistruzione return self; Listruzione return restituisce il valore che segue, quindi in questo caso restituisce self. Cosa vale self? Vale loggetto inizializzato dal metodo della superclasse ([super init]) ed eventualmente con

ulteriori assegnamenti alle variabili di istanza fatti dentro il metodo init stesso (lo faremo pi avanti). Unultima cosa riguardo a questo aspetto. Abbiamo detto che init restituisce un valore, quindi nella sua intestazione deve esserci lindicazione del tipo del valore restituito -(id)init Ricordiamo che il simbolo - indica che si tratta di un metodo di istanza, infatti lo invochiamo su istanze ([Televisore alloc] , super). id indica il tipo restituito, in realt id indica qualunque tipo; in questo modo questa intestazione del metodo si potr usare per qualunque classe accettando un qualunque tipo come risultato (Televisore, Elettrodomestico, OggettoFisico e qualunque altra classe predefinita da Objective-C). Approfondimento: cerca NSObject nella documentazione Apple ed individua il metodo init. Ora basta con il metodo init, lo abbiamo analizzato a sufficienza. Torniamo al main, nel quale con listruzione tv1=[[Televisore alloc] init]; abbiamo creato un oggetto televisore referenziato dalla variabile tv1. A questo punto possiamo invocare i metodi definiti nella classe Televisore su questa istanza. Scriviamo quindi. [tv1 ricercaCanali]; dopo linizializzazione di tv1. Cosa significa quella istruzione? Ha la solita struttura [istanza nomeMetodo]. Quindi consiste nellinvocazione del metodo ricercaCanali sullistanza tv1. Poi aggiungiamo

[tv1 mostraElenco]; Cosa significa quella istruzione? Ha la solita struttura [istanza nomeMetodo]. Quindi consiste nellinvocazione del metodo mostraElenco sullistanza tv1. Aggiungiamo anche [tv1 mostraIlCanale]; [tv1 alzaIlVolume]; [tv1 abbassaIlVolume]; Cosa significano queste istruzioni? Eseguiamo cliccando sul bottone Run, e guardiamo la console,

abbiamo tutti i messaggi dei metodi invocati Bene, per ora ci fermiamo; nella prossima lezione aggiungeremo nei metodi il codice per modificare lo stato interno del televisore, cio per assegnare e leggere i valori delle variabili di istanza.

10 Aggiungiamo la logica
iOS, Mac, OSX, SDK, Tech Room

Nella scorsa lezione abbiamo costruito le strutture dei metodi che dovranno implementare la logica della nostra applicazione. In questa lezione andremo a scrivere il codice necessario ed analizzeremo i costrutti linguistici necessari. Analizziamo quindi i metodi uno per uno. Ricerca canali. In questo metodo vogliamo modellare quello che succede quando nel nostro televisore appena comperato facciamo la sintonizzazione dei canali. Il risultato finale che nel nostro televisore (reale) troveremo la lista dei canali popolata con tutti quelli ricevibili. Quindi quello che dobbiamo fare nel nostro metodo popolare la struttura che modella lelenco dei canali. Questa struttura la variabile di istanza, dichiarata nella classe Televisore, NSArray *canaliMemorizzati; Ricordate cosa abbiamo fatto con la nostra variabile tv1? Prima labbiamo dichiarata con Televisore *tv1 ma a questo punto era solo un nome senza ancora nessun oggetto associato, per creare loggetto ed associarlo a tv1 abbiamo usato tv1=[[Televisore alloc] init] In maniera analoga noi per ora abbiamo solo la dichiarazione del nome dellelenco, ma dobbiamo creare ed inizializzare loggetto ed assegnarlo alla nostra variabile.

Approfondimento: cerca NSArray nella documentazione Apple ed individua i metodi di inizializzazione (iniziano con init). Nel metodo ricercaCanali, dopo la riga NSLog( scriviamo canaliMemorizzati=[[NSArray alloc] initWithObjects:@"Onda1", @"Onda2", @"Onda3", @"Onda4", @"Onda5", nil]; Analizziamo questa istruzione. E unistruzione di assegnamento (stiamo usando il segno =), quindi a sinistra c la variabile cui vogliamo assegnare un valore e a destra c il valore da assegnare. Cosa c a destra? La parte interna [NSArray alloc], linvocazione del metodo alloc sulla classe NSArray; un metodo che abbiamo gi incontrato e che serve a creare unistanza della classe su cui invocato e restituisce listanza creata. Quindi sullistanza creata da [NSArray alloc] si invoca il metodo initWithObjects, questo un metodo che inserisce nellelenco su cui viene invocato gli oggetti che seguono. Nel nostro caso inserir nellelenco appena creato le 5 parole (oggetti anche esse e poi vedremo di cosa) Onda1, Onda2, Onda3, Onda4, Onda5 che possiamo immaginare siano i nomi dei canali ricevibili. Lelenco deve essere terminato da nil che una keyword Objective-C per indicare nessun oggetto e si usa in questo caso per indicare quando finisce la lista degli oggetti da caricare nellelenco. Quindi dopo questa istruzione la nostra variabile di istanza canaliMemorizzati si riferisce ad un oggetto della classe NSArray che modella l elenco (Onda1, Onda2, Onda3, Onda4, Onda5). Mostra elenco. In questo metodo ci faremo scrivere a console il nome dei canali che abbiamo inserito nel nostro elenco. Quindi vogliamo scrivere il codice per modellare una logica del tipo per ogni nome presente nellelenco canaliMemorizzati scrivi il nome

la frase per ogni indicativa di un costrutto linguistico che abbiamo introdotto quando abbiamo parlato dei flussi di controllo e che si chiama ciclo. Cio quello che dobbiamo implementare un ciclo che ripete un certo blocco di istruzioni per ogni elemento dellelenco. Scriviamo il codice e poi lo analizziamo. Allinterno del metodo mostraElenco dopo la riga NSLog( scriviamo for (nomeCanale in canaliMemorizzati) { NSLog(@"Canale: %@",nomeCanale); } (Per ora non preoccupatevi dellerrore che vi viene segnalato, ci arriviamo fra un attimo). Listruzione for una delle istruzioni per implementare un ciclo e ha due forme, quella che stiamo usando scorre un elenco di oggetti ed assegna di volta in volta uno degli oggetti dellelenco ad una variabile e poi esegue un blocco di istruzioni. La sua struttura generale for (variabile in elenco) { <usa variabile> } in pratica ad ogni iterazione del ciclo, alla variabile viene assegnato uno degli oggetti dellelenco e poi viene eseguito il blocco di istruzioni (ricordiamo che un blocco una sequenza di istruzioni racchiuse tra {}). Bene da cosa composto il nostro blocco di istruzioni? Quali istruzioni ci sono dentro le {} che seguono lintestazione del for? Prima di rispondere vediamo cosa vorremmo fare. Diciamo che per il momento ci interessa che per ogni canale nellelenco il programma scrive Canale: con il nome del canale al posto dei puntini. Esattamente con

questo concetto dovremo riuscire a costruire unistruzione che possa scrivere una stringa di caratteri che per sia composta (almeno in parte) solo al momento dellesecuzione; cio al momento in cui scriviamo il programma noi non siamo in grado di preparare completamente la stringa poich solo al momento dellesecuzione avremo valorizzato il nome del canale che vogliamo stampare. In Objective-C si utilizzano delle stringhe particolari, chiamate format strings, che contengono al loro interno dei caratteri speciali che indicano un segnaposto, cio una posizione della stringa che sar valorizzata solo al momento dellesecuzione. Guardiamo listruzione che abbiamo nel nostro blocco del ciclo NSLog(@"Canale: %@",nomeCanale); NSLog la dovremmo conoscere, listruzione per scrivere a console, labbiamo usata ormai diverse volte. Ma cosa scrive questa volta? Canale: %@ i caratteri %@ individuano un segnaposto, cio una posizione della stringa che sar valorizzata al momento dellesecuzione; ma da che valore sar sostituito il segnaposto? Dalla variabile che segue la format string allinterno delle parentesi () della NSLog, quindi nel nostro caso nomeCanale. Cio scriver Canale: seguito dal valore della variabile nomeCanale che viene valorizzata dal ciclo for con i diversi oggetto presenti nellelenco uno alla volta. Veniamo adesso allerrore segnalato da XCode. Abbiamo detto che nel ciclo for stiamo usando la variabile nomeCanale; ma non abbiamo detto al compilatore chi nomeCanale, cio non abbiamo dichiarato la variabile; ricordate che per usare una variabile sempre necessario prima dichiararla. Cio dire che esiste (il nome) e dire di che tipo . Quindi prima della riga con listruzione for aggiungiamo NSString *nomeCanale; Questa una dichiarazione di variabile come ne abbiamo viste diverse quando abbiamo dichiarato le variabili di istanza delle nostre classi. Stiamo dichiarando che esiste una variabile che si

chiama nomeCanale che si riferisce a oggetti di tipo NSString. NSString una classe predefinita in Objective-C e serve a modellare le informazioni di testo, cio le sequenze di caratteri (parole, frasi). Approfondimento: cerca e studia NSString nella documentazione Apple. Ma perch abbiamo scelto questo tipo? Ricordiamo che gli oggetti che abbiamo usato per popolare il nostro elenco sono @Onda1, @Onda2, @Onda3, @Onda4, @Onda5, (riguardiamo il metodo ricercaCanali). Sono cio sequenze di caratteri precedute dalloperatore @, questo operatore rende quelle sequenze di caratteri proprio degli oggetti della classe NSString per cui la nostra variabile del ciclo, nomeCanale, sar dello stesso tipo degli oggetti. Bene, ora proviamo ad eseguire il nostro programma cliccando sul bottone Run e guardiamo la console.

Appaiono le stesse scritte di prima, cio quelle che indicano quale metodo in esecuzione, in pi durante lesecuzione del metodo mostraElenco troviamo le righe 2011-05-23 18:49:42.765 PrimoProgetto[19098:903] Canale: Onda1 2011-05-23 18:49:42.765 PrimoProgetto[19098:903] Canale: Onda2 2011-05-23 18:49:42.766 PrimoProgetto[19098:903] Canale: Onda3 2011-05-23 18:49:42.766 PrimoProgetto[19098:903] Canale: Onda4 2011-0523 18:49:42.767 PrimoProgetto[19098:903] Canale: Onda5 queste stampe sono leffetto dallistruzione NSLog(Canale: %@, nome Canale) allinterno del ciclo nel quale la variabile nomeCanale viene di volta in volta assegnata ad un elemento dellelenco canaliMemorizzati e poi va a sostituire il segnaposto %@ durante lesecuzione dellapplicazione. mostraIlCanale. In questo metodo ci faremo scrivere su quale canale siamo sintonizzati. Ricordiamo che abbiamo scelto di modellare il canale sul quale siamo sintonizzati con una variabile canaleSintonizzato di tipo numerico (si riferir ad istanze di NSDecimalNumber) che conterr il numero della posizione del canale che stiamo guardando nellelenco dei canali memorizzati. Poich gli oggetti NSArray iniziano a contare dalla posizione 0, se siamo sintonizzati sul canale Onda1 la variabile canaleSintonizzato conterr il numero 0, se siamo sintonizzati sul canale Onda2 canaleSintonizzato conterr il numero 1, e cos via. In questo metodo per vogliamo solo farci scrivere il nome del canale sintonizzato. Allinterno delle parentesi { } del metodo nel file Televisore.m, dopo la riga NSLog( aggiungiamo NSLog(@"Stai guardando il canale %@",[canaliMemorizzati

objectAtIndex:[canaleSintonizzato intValue]]); Ora analizziamo questa istruzione. Si tratta di una NSLog, ormai dovremmo conoscerla unistruzione per scrivere qualcosa a console. Cosa scriviamo? La stringa passata come primo argomento. Si tratta di una format string cio di una stringa che contiene un segnaposto (%@), lo abbiamo gi visto nel metodo precedente. Cio quando il programma eseguir questa istruzione scriver Stai guardano il canale Seguito dal valore restituito dallespressione che segue la format string [canaliMemorizzati objectAtIndex:[canaleSintonizzato intValue]] Questa espressione ha la forma del tipo [istanza metodo] cio linvocazione di un metodo su unistanza. In particolare il nostro oggetto canaliMemorizzati, cio unistanza della classe NSArray ed il metodo che invochiamo, un metodo predefinito di Objective-C che si chiama objectAtIndex che restituisce lelemento dellelenco che si trova nella posizione indicata. Approfondimento: cerca NSArray nella documentazione Apple e studia il metodo objectAtIndex. Come si fa ad indicare la posizione dellelemento che vogliamo? Un metodo riceve uninformazione in ingresso per poter funzionare (in questo caso la posizione da leggere) facendo seguire al nome del metodo il simbolo : e poi linformazione necessaria [istanza nomeMetodo:argomento] Se ha bisogno di pi di un argomento avremo diverse coppie nome:argomento, ad esempio se volessimo scrivere un metodo che

fa la somma e quindi restituisce un oggetto numerico, questo metodo dovr ricevere due operandi di tipo numerico, sar quindi dichiarato come -(NSDecimalNumber *)somma:(NSDecimalNumber *)operando1 allOperando: (NSDecimalNumber *)operando2 e sar invocato da [ somma:valoreA allOperando:valoreB] dove al momento dellinvocazione valoreA e valoreB saranno due variabili di tipo NSDecimalNumber che rappresentano i due operandi da sommare. Bene chiudiamo la parentesi sui metodi con parametri (ne vedremo molti altri in seguito, quindi riprenderemo questi concetti) e torniamo al nostro objectAtIndex:. Questo metodo abbiamo detto che deve ricevere in ingresso un numero intero (in senso aritmetico) che indica la posizione dellelenco da leggere. Ma noi non abbiamo un numero, in senso aritmetico, ma un oggetto della classe NSDecimalNumber che rappresenta un numero; come passiamo da questo oggetto al numero intero rappresentato? Si tratta di un oggetto, quindi avremo bisogno di un metodo; infatti noi scriviamo [canaleSintonizzato intValue] che vuol dire invoca sulloggetto canaleSintonizzato il metodo intValue; si tratta di un metodo predefinito che restituisce il numero intero rappresentato dalloggetto su cui invocato. Quindi, riepilogando, listruzione NSLog(@"Stai guardando il canale %@",[canaliMemorizzati objectAtIndex:[canaleSintonizzato intValue]]); prende loggetto canaleSintonizzato e ne restituisce il numero intero rappresentato il risultato di 1. viene passato al metodo objectAtIndex come

indicazione della posizione da leggere nellelenco canaliMemorizzati il valore letto dallelenco va a sostituire il segnaposto nella format string la stringa con il nuovo valore viene scritta a console Possiamo chiudere con questo metodo, ma prima di eseguire lapplicazione, dobbiamo osservare una cosa. Scorrendo i metodi scritti finora, noi non abbiamo mai creato un oggetto da assegnare alla variabile canaleSintonizzato, noi finora abbiamo solo dichiarato questa variabile (cio abbiamo detto che esiste e che si riferir ad un istanza della classe NSDecimalNumber). Quindi visto che abbiamo appena scritto il metodo per far scrivere il canale che stiamo guardando, preoccupiamoci anche di assegnargli un valore. Diciamo che modelliamo un comportamento del tipo Ogni volta che creo un televisore lo faccio partire dal canale 0 La creazione di un oggetto riconducibile al metodo init, quindi vuol dire che dobbiamo scrivere qualcosa nel metodo init della classe Televisore, in particolare dovremo assegnare alla variabile canaleSintonizzato lindice 0 come oggetto di NSDecimalNumber. Quindi selezioniamo il file Televisore.m e posizioniamoci allinterno del blocco (ricordiamo che un blocco racchiuso tra {}) che segue if (self) nel metodo init e scriviamo canaleSintonizzato=[NSDecimalNumber zero]; unistruzione di assegnamento (c il simbolo = ne abbiamo viste gi alcune) quindi alla variabile a sinistra del simbolo = viene assegnato il valore dellespressione a destra del simbolo =. Quindi alla variabile canaleSintonizzato viene assegnato il risultato di

[NSDecimalNumber zero] Questa espressione ha la forma di [nomeClasse metodo] si tratta quindi dellinvocazione di un metodo di classe; in particolare zero un metodo predefinito in Objective-C che restituisce unistanza di NSDecimalNumber che rappresenta il valore 0. Approfondimento: cerca NSDecimalNumber nella documentazione Apple e studia i metodi zero e one. Bene, in tal modo abbiamo dato un valore iniziale alla variabile canaleSintonizzato e possiamo ora eseguire nuovamente lapplicazione (cliccando sul bottone Run) e guardare il risultato nella console.

Ai messaggi precedenti si aggiunto il messaggio 2011-05-24 18:57:43.912 PrimoProgetto[99193:903] Stai

guardando il canale Onda1 e Onda1 proprio il canale nella posizione 0 dellelenco. alzaIlVoume In questo metodo andiamo ad aumentare il livello del volume del nostro ipotetico televisore. Per modellare il livello del volume abbiamo dichiarato una variabile di istanza, livelloVolume, di tipo numerico (istanza di NSDecimalNumber). Quindi il nostro metodo dovr semplicemente aumentare di 1 il valore rappresentato da questo oggetto. Per comodit ci faremo anche scrivere il livello del volume prima e dopo lincremento. Quindi allinterno del metodo alzaIlVolume, nel file Televisore.m, scriviamo NSLog (@"Il volume a livello %@", livelloVolume); livelloVolume= [livelloVolume decimalNumberByAdding: [NSDecimalNumber one]]; NSLog (@"Il volume a livello %@",livelloVolume); La prima istruzione un NSLog (ne abbiamo viste tante) cio scriviamo a console qualcosa; in particolare abbiamo una format string costituita da una parte fissa Il volume a livello ed un segnaposto (%@) che durante lesecuzione sar sostituito dal valore delloggetto referenziato dalla variabile livelloVolume. La seconda istruzione unassegnamento (anche qui dovremmo essere ormai in grado di riconoscerla dal simbolo =. Quindi alla variabile a sinistra (livelloVolume) assegnamo il valore risultante dallespressione a destra [livelloVolume decimalNumberByAdding:[NSDecimalNumber one]] Cosa questa istruzione? Siamo ancora nella struttura tipica per la

programmazione Objective-C [istanza metodo] Cio linvocazione di un metodo su unistanza di una classe; in particolare stiamo invocando il metodo decimalNumberByAdding sullistanza livelloVolume. Si tratta di un metodo predefinito della classe NSDecimalNumber che restituisce una nuova istanza della classe ottenuta sommando al valore dellistanza ricevente (livelloVolume) il valore dellargomento del metodo, cio, nel nostro caso [NSDecimalNumber one] Anche qui siamo davanti allinvocazione di un metodo, questa volta di classe in quanto il primo termine una classe e non unistanza, che restituisce loggetto che rappresenta il valore 1. Approfondimento: cerca NSDecimalNumber nella documentazione Apple e studia il metodo decimalNumberByAdding. Quindi, riepilogando, livelloVolume= [livelloVolume decimalNumberByAdding: [NSDecimalNumber one]]; assegna alla variabile livelloVolume un oggetto che rappresenta il valore che si ottiene sommando 1 al valore precedente di livelloVolume. La terza istruzione un NSLog identica alla prima che scrive il nuovo valore del livello del volume. Tutto qui? E un po troppo semplice, manca qualcosa. Cosa succede nel nostro televisore di casa se alziamo il volume? Ovviamente il volume aumenta, e se lo alziamo ancora? Aumenta, e cos via fino a che raggiunge il livello massimo, a quel punto non aumenta pi anche se premiamo il tasto aumenta volume. Bene allora dobbiamo modellare anche questo comportamento nel

nostro programma. Come procediamo? Iniziamo osservando che abbiamo una nuova informazione che il livello massimo del nostro televisore, abbiamo quindi bisogno di una nuova variabile di istanza. Selezioniamo il file Televisore.h e dopo la riga NSDecimalNumber *livelloVolume; scriviamo (prima della }) NSDecimalNumber *volumeMassimo; in questo modo abbiamo dichiarato la variabile che conterr il valore massimo per il livello del volume. Dobbiamo per ancora creare loggetto cui questa variabile si riferisce; il posto pi naturale dove inserire questa creazione il metodo init della classe Televisore, il senso che quando viene creato un televisore anche definito qual il livello massimo di volume. Per il nostro esempio assumiamo che il volume massimo sia 100, e nel metodo init diamo anche un valore iniziale al livello del volume posizionandolo a 50. Quindi apriamo il file Televisore.m e posizioniamoci allinterno del metodo init e subito dopo la riga che abbiamo inserito precedentemente canaleSintonizzato=[NSDecimalNumber zero]; scriviamo volumeMassimo=[[NSDecimalNumber alloc] initWithString:@"100"]; unistruzione di assegnamento, la variabile volumeMassimo prende il valore risultato dallespressione [[NSDecimalNumber alloc] initWithString:@100] una forma che abbiamo gi incontrato e si tratta di una chiamata del metodo di classe alloc [NSDecimalNumber alloc] che crea unistanza della classe NSDecimalNumber e su questa istanza si invoca il metodo

initWithString:@100 questo un metodo predefinito della classe NSDecimalNumber che restituisce listanza sulla quale invocato valorizzata con il valore scritto tra gli , nel nostro caso 100. Approfondimento: cerca NSDecimalNumber nella documentazione Apple e studia i metodi initWithString e initWithMantissa:exponent:isNegative: In maniera analoga inizializziamo a 50 il valore del livello corrente del volume livelloVolume=[[NSDecimalNumber alloc] initWithString:@"50"]; Bene ora che abbiamo ulteriormente configurato il nostro televisore con il livello massimo ed il livello corrente del volume possiamo tornare al nostro metodo alzaIlVolume. Come dobbiamo cambiare il codice? Abbiamo detto che il volume si alza solo se non al massimo, quindi dobbiamo scrivere qualcosa del tipo se il volume non al massimo alza il volume altrimenti non fare nulla Ricordiamo che nel capitolo sui flussi di controllo abbiamo parlato di flusso condizionale cio della possibilit di eseguire un blocco di istruzioni solo se una determinata condizione vera. E proprio il costrutto di cui abbiamo bisogno; vediamo come si implementa in Objective-C. La struttura generale if (condizione) { } else { } se la condizione vera viene eseguito il primo blocco di istruzioni, quello racchiuso tra la prima coppia di {} altrimenti viene eseguito il secondo, quello racchiuso tra la seconda coppia di {}. Esiste una forma semplificata che comprende solo il blocco relativo alla

condizione vera if (condizione) { } nella quale se la condizione falsa non viene eseguita nessuna istruzione. Noi adotteremo la seconda forma. Nel metodo alzaIlVolume sostituiamo la riga livelloVolume= [livelloVolume decimalNumberByAdding: [NSDecimalNumber one]]; con if ([livelloVolume compare:volumeMassimo]==NSOrderedAscending) { livelloVolume= [livelloVolume decimalNumberByAdding: [NSDecimalNumber one]]; } in pratica abbiamo racchiuso listruzione di incremento del livello del volume dentro un blocco controllato dalla veridicit di una condizione. Analizziamo ora la condizione [livelloVolume compare:volumeMassimo]==NSOrderedAscending E un controllo di uguaglianza, loperatore == confronta i due termini, quello a sinistra e quello a destra e restituisce vero se sono uguali, falso se sono diversi. Cosa c a sinistra? [livelloVolume compare:volumeMassimo] solita invocazione di un metodo di istanza su un oggetto (livelloVolume). Il metodo compare che prende in ingresso un oggetto NSDecimalNumber (volumeMassimo) e lo confronta con loggetto ricevente linvocazione, cio livelloVolume. In pratica confronta i valori di livelloVolume e volumeMassimo e restituisce NSOrderedAscending se il primo minore del secondo, NSOrderedSame se sono uguali e NSOrderedDescending se il

primo maggiore del secondo. Quindi la condizione [livelloVolume compare:volumeMassimo]==NSOrderedAscending sar vera quando livelloVolume minore di volumeMassimo, ed in questo caso sar eseguito il blocco di istruzioni, cio lincremento del volume. Quindi if ([livelloVolume compare:volumeMassimo]==NSOrderedAscending) { livelloVolume= [livelloVolume decimalNumberByAdding: [NSDecimalNumber one]]; } confronta livelloVolume e volumeMassimo e solo se il primo minore del secondo incrementa il volume. Prima di eseguire il programma, per vedere meglio leffetto di quello che abbiamo fatto, torniamo nel main.m e ripetiamo per tre volte listruzione alzaIlVolume [tv1 alzaIlVolume]; [tv1 alzaIlVolume]; [tv1 alzaIlVolume]; Ora eseguiamo il programma cliccando sul bottone Run ed osserviamo la console.

Vediamo i messaggi effetto del volume che parte da 50 (il valore nel metodo init) ed aumenta. abbassaIlVoume In questo metodo andiamo a diminuire il livello del volume del nostro ipotetico televisore. Come potete immaginare un metodo molto simile al precedente. Anche questo metodo interviene sulla variabile di istanza livelloVolume, e dovr semplicemente diminuire di 1 il suo valore. Ovviamente anche qui dovremo introdurre un controllo per impedire che il volume possa scendere sotto il valore 0. Quindi allinterno del metodo, nel file Televisore.m, scriviamo NSLog (@"Il volume a livello %@", livelloVolume); if ([livelloVolume compare:[NSDecimalNumber zero]]==NSOrderedDescending) { livelloVolume= [livelloVolume decimalNumberBySubtracting: [NSDecimalNumber one]]; } NSLog (@"Il volume a livello %@",livelloVolume);

La prima e lultima istruzione sono gli NSLog, con format string per far scrivere il livello del volume e sono le stesse del metodo precedente. if ([livelloVolume compare:[NSDecimalNumber zero]]==NSOrderedDescending) { livelloVolume= [livelloVolume decimalNumberBySubtracting: [NSDecimalNumber one]]; } unistruzione per un flusso di controllo condizionale, cio il blocco di istruzioni, racchiuso tra le {} eseguito solo se la condizione racchiusa tra le ( ) vera. La condizione [livelloVolume compare:[NSDecimalNumber zero]]==NSOrderedDescending un controllo di uguaglianza, loperatore == confronta i due termini, quello a sinistra e quello a destra e restituisce vero se sono uguali, falso se sono diversi. Cosa c a sinistra? [livelloVolume compare:[NSDecimalNumber zero]] la solita invocazione di un metodo di istanza su un oggetto (livelloVolume). Il metodo compare che prende in ingresso un oggetto NSDecimalNumber ([NSDecimalNumber zero]) e lo confronta con loggetto ricevente linvocazione, cio livelloVolume. [NSDecimalNumber zero] lo abbiamo gi incontrato ed linvocazione del metodo di classe zero sulla classe NSDecimalNumber; questo metodo restituisce unistanza di NSDecimalNumber che rappresenta il numero 0. In pratica confronta il valore di livelloVolume con 0 e restituisce NSOrderedAscending se il primo minore del secondo,

NSOrderedSame se sono uguali e NSOrderedDescending se il primo maggiore del secondo. Quindi la condizione [livelloVolume compare:[NSDecimalNumber zero]]==NSOrderedDescending sar vera quando il valore di livelloVolume maggiore di 0, ed in questo caso sar eseguito il blocco di istruzioni, cio la diminuzione del volume. Prima di eseguire il programma, per vedere meglio leffetto di quello che abbiamo fatto, torniamo nel main.m e ripetiamo per tre volte listruzione abbassaIlVolume [tv1 abbassaIlVolume]; [tv1 abbassaIlVolume]; [tv1 abbassaIlVolume]; Eseguiamo il programma e guardiamo la console.

Vediamo i messaggi relativi al volume che dopo esser arrivato a 53, per effetto d alzaIlVolume, torna a 50 con le invocazioni di abbassa il volume. vaiAlCanale

In questo metodo sintonizziamo il televisore ad un canale specifico. Una prima considerazione che questo metodo per funzionare ha bisogno di ricevere uninformazione al momento dellinvocazione, cio il numero del canale da sintonizzare. Per fare questo necessario implementare un metodo che riceva dei parametri, infatti la sua dichiarazione, la troviamo nel file Televisore.h, -(void)vaiAlCanale:(NSDecimalNumber *)canaleDaSintonizzare; che vuol dire che il metodo vaiAlCanale al momento dellinvocazione ricever un valore istanza di NSDecimalNumber che sar referenziato allinterno del metodo con il nome canaleDaSintonizzare. Allinterno del blocco del metodo vaiAlCanale nel file Televisore.m, dopo listruzione NSLog(@"Sto eseguendo il metodo vaiAlCanale"); scriviamo canaleSintonizzato=canaleDaSintonizzare; E unistruzione di assegnamento, in pratica alla variabile distanza canaleSintonizzato assegnamo il valore che ricever al momento dellinvocazione (canaleDaSintonizzare). Vediamo ora come sar linvocazione. Torniamo nel file main.m e, dopo lultima istruzione [tv1 abbassaIlVolume]; scriviamo [tv1 vaiAlCanale:[[NSDecimalNumber alloc] initWithString:@"3"]]; cio diciamo allistanza tv1 di eseguire il metodo vaiAlCanale passandogli come argomento

[[NSDecimalNumber alloc] initWithString:@3] unespressione che abbiamo gi incontrato, [NSDecimalNumber alloc] crea unistanza di NSDecimalNumber, su questa istanza invochiamo il metodo initWithString:@3 che la inizializza con il valore 3. Infine per verificare se effettivamente il televisore ha cambiato canale, invochiamo nuovamente il metodo per farci scrivere il canale sintonizzato, aggiungendo listruzione [tv1 mostraIlCanale]; Eseguiamo il programma ed osserviamo la console.

Vediamo che c il messaggio 2011-05-25 22:39:46.358 PrimoProgetto[98714:903] Stai guardando il canale Onda4 ed effettivamete Onda4 il canale in posizione 3. Per ora fermiamoci, questo capitolo stato veramente lungo e pieno di nuovi concetti, per cui vi invito a ripercorrerlo con attenzione, facendo anche delle prove per vedere gli effetti delle

vostre modifiche. 11 Riepilogo ed altro


iOS, Mac, OSX, SDK, Tech Room

Facciamo un po il punto focalizzando i concetti principali visti finora e facendo alcuni approfondimenti. Il modello OO Abbiamo visto che la programmazione secondo il modello OO prevede la descrizione del contesto secondo il meccanismo delle classi e delle istanze. Le classi descrivono la struttura degli oggetti in termini di attributi (variabili di istanza) e funzionalit (metodi). La definizione di una classe effettuata attraverso due file <nome classe>.h: che contiene solo le dichiarazioni degli elementi caratterizzanti la classe; <nome classe>.m: che contiene limplementazione dei metodi; La sintassi per linvocazione di un metodo, e quindi per richiedere una certa funzionalit ad un oggetto o ad una classe [oggetto metodoDiIstanza]; [classe metodoDiClasse]; I metodi di istanza sono quelli che sono eseguiti dagli oggetti di una classe e che modellano il comportamento stesso degli oggetti (alzaIlVolume, vaiAlCanale,). I metodi di classe invece sono eseguiti dalle classi (e non dalle istanze) e di solito si usano per creare istanze della classe (alloc,). Facciamo un piccolo esercizio per chiarire ancora meglio la differenza tra classe (una) e istanze (tante). Nel nostro esempio abbiamo per ora un solo televisore, ora creiamo altri due televisori

dichiarando due altra variabili tv2 e tv3, allocando ed inizializzando gli oggetti assegnati alle variabili (guarda come abbiamo fatto per tv1) e su queste due nuove istanze invoca i metodi che vuoi analizzando loutput e osservando che le modifiche fatte su unistanza non si ripercuotono sulle altre: se cambio il canale di tv1non cambia quello in tv2, se alzo il volume in tv2 non cambia il volume in tv3, Gerarchia Nel nostro programma abbiamo definito la classe Televisore come sottoclasse di Elettrodomestico (caratterizzato da un consumo) a sua volta sottoclasse di OggettoFisico (caratterizzato da un peso) ma durante lo sviluppo del codice non abbiamo mai fatto uso di queste informazioni. Ora supponiamo di voler modellare il fatto che ogni televisore della nostra classe consumi 500W e pesi 20Kg. Apriamo il file Televisore.m e andiamo nel metodo init, dopo listruzione livelloVolume=[[NSDecimalNumber alloc] initWithString:@"50"]; aggiungiamo consumo = [[NSDecimalNumber alloc] initWithString:@"500"]; Cosa abbiamo fatto? Abbiamo cercato di assegnare un valore (un oggetto che rappresenta il valore 500) ad una variabile della superclasse. In effetti in questo momento XCode ci segnala un errore e se guardiamo lerrore segnalato, ci dice che consumo una variabile privata.

Quando costruiamo una gerarchia possiamo decidere quali elementi della superclasse possono essere ereditati dalle sottoclassi e questo si fa facendo precedere @private allelenco delle variabili che non vogliamo che siano ereditate. Se torniamo sul file Elettrodomestico.h vediamo che prima della dichiarazione della variabile consumo, c proprio la keyword @private, cancelliamola e verifichiamo che lerrore sia sparito. Facciamo la stessa cosa per il peso aggiungendo la riga peso = [[NSDecimalNumber alloc] initWithString:@"20"]; nel metodo Init della classe Televisore e togliendo @private da OggettoFisico.h. Infine, tanto per vedere il risultato di quello che abbiamo fatto, ci definiamo un nuovo metodo che chiamiamo mostraInfo che ci stamper le caratteristiche del televisore. Nel file Televisore.h aggiungiamo alla lista dei metodi la dichiarazione del nuovo metodo

-(void)mostraInfo; che ci stamper le caratteristiche del televisore; in Televisore.m aggiungiamo (tra la } di un metodo e linizio del metodo successivo) la sua implementazione -(void)mostraInfo { NSLog(@"Il televisore pesa %@ Kg e consuma %@ W",peso,consumo); } Questo metodo non fa altro che scrivere a console la frase Il Televisore pesa Kg e consuma W come vedete abbiamo usato una format string con 2 segnaposto che durante lesecuzione saranno sostituiti con il valore di peso e consumo. Bene, per veder leffetto di questo metodo che dobbiamo fare? Ovviamente lo dobbiamo invocare quindi torniamo nel nostro main.m e aggiungiamo, in un punto qualsiasi, purch sia dopo linizializzazione di tv1 [tv1 mostraInfo]; Eseguiamo il programma tramite il bottone Run e guardiamo la console sulla quale adesso apparir anche il messaggio Il televisore pesa 20 Kg e consuma 500 W Abbiamo appena visto che tramite una gerarchia una sottoclasse pu usare anche le variabili di istanza (non private) delle superclassi. Ma cosa succede dei metodi? Aggiungiamo un nuovo comportamento al nostro esempio, supponiamo di voler modellare il fatto che un elettrodomestico si pu accendere o spegnere; questo significa che in ogni istante il mio elettrodomestico in uno dei due stati e inoltre che ho un meccanismo per fargli cambiare stato. Dal punto di vista della programmazione abbiamo quindi una

nuova informazione da gestire, lo stato. Una nuova informazione richiede una nuova variabile di istanza; abbiamo diverse opportunit di scelta che dipendono dai gusti personali una stringa che pu assumere i valori Acceso o Spento una variabile che pu assumere i valore Vero o Falso per dirci se acceso una variabile che pu assumere i valore Vero o Falso per dirci se spento scegliamo la seconda, cio avremo una variabile che ci dir se vero o falso che acceso. In Objective-C i valori vero/falso determinano un tipo di dato chiamato BOOL. Cio le variabili di tipo BOOL possono assumere solo due valori che il computer interpreta come Vero o Falso. Attenzione: a differenza dei tipi visti finora, BOOL non un a classe e le sue variabili non sono oggetti; si tratta di un tipo di dato scalare come ne vedremo altri pi avanti per quanto riguarda i numeri. Quindi andiamo a dichiarare la nostra variabile; nel file Elettrodomestico.h (sempre allinterno delle {}) aggiungiamo BOOL acceso; abbiamo dichiarato una variabile di nome acceso e di tipo BOOL (Osserviamo che non trattandosi di una classe ma di un tipo elementare non c il carattere * prima del nome della variabile). Poi la prima cosa che facciamo che quando costruiamo un elettrodomestico questo spento, quindi nel metodo init di Elettrodomestico.m, allinterno delle {} dopo listruzione if (self) aggiungiamo acceso=FALSE; cio falso che lelettrodomestico sia acceso.

Adesso per dobbiamo modellare il nostro interruttore; nel file Elettrodomestico.h aggiungiamo la dichiarazione di 2 metodi -(void)accendi; -(void)spegni; con questi due metodi accenderemo e spegneremo il nostro elettrodomestico andando semplicemente a cambiare il valore Vero/Falso della variabile acceso, per comodit ci faremo anche scrivere in che stato lelettrodomestico. Nel file Elettrodomestico.m aggiungiamo le due definizioni dei metodi -(void)accendi { acceso=TRUE; NSLog(@"L'elettrodomestico acceso"); } -(void)spegni { acceso=FALSE; NSLog(@"L'elettrodomestico spento"); } e infine nel file main.m aggiungiamo, dopo [tv1 mostraInfo] [tv1 accendi]; e prima di [pool drain] [tv1 spegni]; Eseguiamo e osserviamo i messaggi, vediamo quindi che grazie allereditariet, anche i metodi delle superclassi (metodi definiti in Elettrodomestico) si possono usare nelle sottoclassi (tv1 un Televisore). Un meccanismo importante legato a questo aspetto il fatto che possibile comunque specializzare un metodo ereditato. Supponiamo di voler modellare il fatto che quando accendiamo un televisore, oltre cambiare di stato acceso/spento ricerca i canali e

ci mostra lelenco di quelli sintonizzati. Andiamo in Televisore.m -(void)accendi { [super accendi]; [self ricercaCanali]; [self mostraElenco]; } come vedete non abbiamo avuto bisogno di dichiarare il metodo accendi allinterno di Televisore.h, in quanto lo eredita da Elettrodomestico, ma ne abbiamo cambiato la sua implementazione. In particolare la prima cosa che facciamo che accendiamo il nostro televisore in quanto elettrodomestico [super accendi]; questo ne cambia lo stato della variabile acceso, poi invochiamo i metodi propri della classe Televisore [self ricercaCanali]; [self mostraElenco]; Ripeto una cosa gi detta, super e self sono sempre lo stesso oggetto (il mio televisore) ma una volta (super) agisco su di esso come Elettrodomestico e laltra invece come Televisore ma loggetto sempre lo stesso, non esiste alcun super-oggetto Elettrodomestico e un altro super-oggetto OggettoFisico, ma solo superclassi. Ora per semplificare un po loutput, visto che linvocazione dei ricercaCanali e mostraElenco labbiamo inserite in accendi togliamo le righe corrispondenti dal main.m, eseguimo ed osserviamo loutput. Infine, per quanto riguarda la gerarchia, dobbiamo dare un senso allo stato accesso/spento. Noi abbiamo tanti metodi, -(void)ricercaCanali;

-(void)mostraElenco; -(void)mostraIlCanale; che in questo momento non tengono conto se il televisore acceso o spento; per fare una cosa corretta dovremo tutti trasformarli in se il televisore acceso fai quello che devi fare altrimenti non fare nulla una struttura di tipo condizionale, cio un blocco di istruzioni viene eseguito solo se si verifica una determinata condizione, nel nostro caso solo se la variabile acceso Vero. Vi mostro come cambia il metodo mostraIlCanale, e vi lascio la modifica degli altri. -(void)mostraIlCanale { NSLog(@"Sto eseguendo il metodo mostraIlCanale"); if (acceso) { NSLog(@"Stai guardando il canale %@",[canaliMemorizzati objectAtIndex:[canaleSintonizzato intValue]]); } } cio listruzione che scrive il canale che stiamo guardando viene racchiusa in un blocco controllato dal valore della variabile acceso, quindi se acceso vale Vero, sar eseguita altrimenti no. Una volta fatte le modifiche provate ad eseguire il programma sia con listruzione [tv1 accendi] nel main.m sia senza ed osservate i diversi output. Aritmetica Quando abbiamo avuto bisogno di informazioni numeriche nel nostro programma (numero del canale, livello del volume, peso,

consumo) abbiamo sempre usato variabili cui associare oggetti della classe NSDecimalNumber, in effetti questa la classe che Objective-C mette a disposizione proprio per questo scopo. Come abbiamo creato finora gli oggetti di questa classe? [[NSDecimalNumber alloc] initWithString:@] scrivendo il valore che volevamo rappresentare al posto dei puntini. Per laccesso ai valori abbiamo invece usato [nomeIstanza intValue] per ottenere il valore intero contenuto nelloggetto. Approfondimento: Cerca sulla documentazione Apple la classe NSDecimalNumber, osserva che sottoclasse di NSNumber e guarda la classe NSNumber, in particolare i metodi costruttori ed inizializzatori Solitamente nei programmi Objective-C per la gestione delle informazioni numeriche si fa ricorso a tipi di dato scalari, cio a tipi dii dato che fanno riferimento a valori piuttosto che a oggetti. Quando trattiamo uninformazione numerica con i tipi scalari, la prima considerazione che dobbiamo fare se si tratta di un numero intero o decimale. Nel primo caso in Objective-C, disponibile il tipo int nel secondo caso sono disponibili i tipi float e double Per valori particolarmente grandi si antepone la keyword long, inoltre possibile usare la keyword unsigned per indicare variabili che assumeranno solo valori positivi. In generale i valori rappresentabili in questi tipi di dato dipendono dalle diverse

implementazioni dei SO e del HW su cui sono compilati; possiamo in linea di massima dire che gli intervalli di valori possibili sono: int : [-2^31..2^31-1] unsigned int: [0..2^32-1] long int: [-2^63..2^63-1] unsigned long int: [0..2^64-1] Per quanto riguarda i valori float e double, non mia intenzione entrare in questa guida nel merito della rappresentazione in virgola mobile per cui vi rimando alla descrizione nella documentazione ed in particolare aprendo lapplicazione Terminal e digitando man float. Luso dei tipi scalari ha un impatto anche sulle format string. Ricordate che per far stampare un numero (istanza di NSDecimalNumber) noi abbiamo usato come segnaposto il simbolo %@. Se invece stiamo usando valori int, il segnaposto da usare sar %d; nel caso di valori float (o double) sar %f. Nei segnaposto %f, per i numeri con una parte decimale, possiamo anche specificare quante cifre decimali vogliamo vedere, cio il segnaposto sar del tipo %.2f che vorr dire che vogliamo che siano stampate 2 cifre decimali. Tornando al nostro programma vediamo come si sarebbero potuti usare i valori scalari int invece degli oggetti NSDecimalNumber. Modifichiamo ad esempio il livello del volume. Prima cosa andiamo nel file Televisore.h e modifichiamo le due righe di livelloVolume e livelloMassimo come segue // NSDecimalNumber *livelloVolume; // NSDecimalNumber *volumeMassimo; cio le facciamo precedere da una coppia di slash //; questo trasforma le due righe in commenti, cio il compilatore non le prende in considerazione, ma per noi sar utile tenerle l se poi in

seguito vorremo tornare a questa modalit di programmazione. Subito dopo le due righe appena commentate aggiungiamo int livelloVolume; int volumeMassimo; cio abbiamo dichiarato due variabili con lo stesso nome delle precedenti per stavolta di tipo int. Osserviamo che non trattandosi di classi non c bisogno del simbolo * prima del nome della variabile. Ora andiamo nel file Televisore.m a modificare il codice dei metodi init, alzaIlVolume ed abbassaIlVolume. Iniziamo con il metodo init. Individuiamo le due righe che assegnano i valori iniziali a livelloVolume e livelloMassimo e che ora sono segnalate da un warning in quanto gli assegnamenti di oggetti non sono pi compatibili con il tipo delle due variabili che abbiamo dichiarato int. Come prima, commentiamo le due istruzioni anteponendo // ad entrambe le righe e subito dopo esse aggiungiamo le nuove inizializzazioni volumeMassimo=100; livelloVolume=50; alla variabile viene assegnato direttamente il valore numerico. Passiamo ai due metodi per alzare ed abbassare il volume, come abbiamo fatto per le variabili per teniamoci da parte la versione attuale facendola diventare un commento. Per fare questo, trattandosi di pi righe, invece di anteporre // ad ogni riga aggiungiamo /* prima della definizione del metodo alzaIlVolume e aggiungiamo */ dopo la fine della definizione del metodo abbassaIlVolume; dovrete avere qualcosa del tipo illustrato in figura.

Bene a questo punto dobbiamo ridefinirci i due metodi che per stavolta faranno uso di variabili di tipo int. Questo il nuovo alzaIlVolume -(void)alzaIlVolume { NSLog(@"Sto eseguendo il metodo alzaIlVolume"); if (acceso) { NSLog (@"Il volume a livello %d", livelloVolume); if (livelloVolume < volumeMassimo) { livelloVolume= livelloVolume+1; } NSLog (@"Il volume a livello %d",livelloVolume); } } Prima cosa da notare la format string che ora contiene il segnaposto %d dovendo ricevere un valore int. Il test di condizione usa loperatore < (minore) che restituisce vero se il termine a sinistra minore di quello a destra. Gli operatori di confronto sono Listruzione di incremento del livello del volume usa loperatore aritmetico +. Gli operatori aritmetici sono

Unattenzione particolare merita la divisione /. Se la divisione viene applicata a due valori interi loperazione sar una divisione intera, cio con quoziente intero; se invece uno dei due operandi float loperazione restituir un quoziente decimale. Infine il metodo -(void)abbassaIlVolume { NSLog(@"Sto eseguendo il metodo abbassaIlVolume"); if (acceso) { NSLog (@"Il volume a livello %d", livelloVolume); if (livelloVolume > 0) { livelloVolume= livelloVolume-1; } NSLog (@"Il volume a livello %d",livelloVolume); } } valgono le stesse considerazioni del metodo precedente. Bene, se ora proviamo ad eseguire il programma vediamo che otteniamo gli stessi risultati precedenti. Questa una lezione importante, quando facciamo un programma non c un unico modo di fare le cose. Per esercizio ora provate a cambiare tutti gli NSDecimalNumber in int. Nel prossimo articolo introdurremo la comunicazione tra due oggetti.

12 Comunicazione tra oggetti


iOS, Mac, OSX, SDK, Tech Room

Riprendiamo lo sviluppo del nostro televisore. Abbiamo implementato le funzioni principali che ci interessano, ma per fargli fare qualcosa agiamo direttamente sulle sue funzionalit, invochiamo direttamente i metodi di Televisore, come se per cambiare canale o alzare il volume ogni volta dovessimo alzarci dalla poltrona e girare qualche manopola della tv. E il momento di acquistare un telecomando e farlo comunicare con la nostra tv. Abbiamo bisogno quindi di un nuovo tipo di oggetto; in Objective-C questo si traduce nella definizione di una nuova classe. Facendo click con il bottone destro sulla cartella con il nome del progetto in XCode scegliamo New File e poi il template Objective-C Class del gruppo Cocoa Touch nella sezione iOS, lasciamo NSObject come superclasse e chiamiamo la nuova classe Telecomando. Alla fine della procedura di creazione avremo due nuovi file nel nostro progetto Telecomando.h dove scriveremo le dichiarazioni e Telecomando.m dove scriveremo le implementazioni. Iniziamo con il file header (Telecomando.h); ricordiamo che allinterno delle {} dovremo dichiarare le variabili di istanza che modellano le informazioni caratteristiche della classe, mentre prima di @end dovremo dichiarare i metodi che modellano le funzionalit della classe.

Che informazioni gestisce un telecomando?il volume, la lista dei canali, il canale sintonizzato, sono tutte informazioni del televisore, non sono nel telecomando; quindi come prima ipotesi (pi avanti vedremo che non vera) diciamo che il telecomando non ha informazioni proprie e quindi non dichiariamo nessuna variabile di istanza. Passiamo ai metodi. Il nostro telecomando dovr essere in grado di accendere e spegnere il televisore, di alzarne o abbassarne il volume e di cambiarne il canale. Ho evidenziato alcune parti della descrizione che implicano che i metodi del telecomando dovranno modificare lo stato del televisore, ma per la regola dellincapsulamento, lo potr fare solo invocando i metodi del televisore. Iniziamo quindi a dichiarare i metodi del telecomando, nel file Telecomando.h prima della riga @end scriviamo -(void)accendiIlTelevisore; -(void)spegniIlTelevisore; -(void)mostraIlCanaleDelTelevisore; -(void)alzaIlVolumeDelTelevisore; -(void)abbassaIlVolumeDelTelevisore; -(void)vaiAlCanale:(NSDecimalNumber *)canaleDaSintonizzare; Ricordiamo brevemente la struttura della dichiarazione di un metodo: Il simbolo - indica che stiamo dichiarando un metodo di istanza, cio un metodo che sar eseguito da unistanza della classe; (void) indica il tipo del risultato dellesecuzione del metodo, in particolare void indica nessun tipo, poich i nostri metodi non restituiscono nessun dato, ma agiscono solo sullo stato degli oggetti;

poi c il nome del metodo se un metodo richiede uninformazione al momento dellinvocazione per poter essere eseguito (vaiAlCanale), il nome seguita da : e lindicazione del parametro espresso come (tipo del parametro), quando si tratta di una classe metteremo il simbolo * nome del parametro Ho tralasciato limplementazione di alcuni metodi che potranno essere invocati solo direttamente dal televisore, ma se preferite potete fare un telecomando pi potente ed implementarli tutti; unaltra osservazione riguarda il metodo vaiAlCanale che prende come parametro un numero, come abbiamo visto nellarticolo precedente per gestire informazioni numeriche si possono usare anche tipi di dato elementare (int, float, double), per gusto personale io preferisco in genere usare oggetti per gestire informazioni numeriche non espressamente aritmetiche visto che stiamo programmando in OO e ricorrere ai tipi elementari quando avremo bisogno di variabili di supporto (ad esempio contatori) come vedremo nei prossimi articoli. Ma se preferite usare le variabili semplici vi lascio ladattamento a questo modello come esercizio. Ora andiamo a preparare la struttura dei metodi nel file di implementazione (Telecomando.m). Per ognuno dei metodi ripetiamo lintestazione (senza il ed aggiungiamo una coppia di { } per racchiudere il blocco di istruzioni del metodo. Es. -(void)accendiIlTelevisore { } Bene, iniziamo proprio dallimplementazione del metodo accendiIlTelevisore.

Prima cosa ci facciamo scrivere che il metodo in esecuzione, tra le { } scriviamo NSLog(@"Il telecomando sta accendendo il televisore"); Poi quello che dobbiamo fare far accendere il televisore dal telecomando, questo lo sappiamo fare, sufficiente invocare il metodo accendi di Televisore. Gi ma su quale istanza lo invochiamo? La risposta a questa domanda nasconde una regola importante: perch due oggetti comunichino necessario che uno abbia il riferimento dellaltro. Come tutte le regole avr la sua eccezione ma ci arriveremo solo pi avanti. Cosa succede quando vogliamo usare il nostro telecomando? Dobbiamo puntarlo verso il ricevitore ad infrarossi del televisore; cio il telecomando deve conoscere e vedere il televisore che sta pilotando. Quindi il telecomando deve avere un riferimento al televisore. Sembrerebbe naturale dire che noi abbiamo gi un riferimento che tv1; quindi proviamo a scrivere, sotto listruzione NSLog che abbiamo appena inserito [tv1 accendi]; cio diciamo che il nostro telecomando, quando invocheremo il metodo accendiIlTelevisore, accender tv1; ma XCode ci segnala un errore, che ci dice che non conosce tv1. Nelluso delle variabili c una caratteristica fondamentale che quella della visibilit. Cio in quali parti del programma utilizzabile una variabile. La regola abbastanza semplice, una variabile visibile allinterno del blocco ( { } ) nel quale stata dichiarata. La nostra variabile tv1 dichiarata nel blocco main ed ora siamo nel blocco accendiIlTelevisore quindi qui la variabile tv1 non visibile e XCode ce lo dice. Implementiamo quindi una nostra variabile di istanza, nella classe

Telecomando, che conterr il riferimento al televisore da pilotare e che sar usata da tutti i metodi di Telecomando. Nel file Telecomando.h, allinterno delle { } scriviamo Televisore *myTv; XCode segnala ancora un errore, perch? Perch la classe Telecomando non conosce la classe Televisore, quindi non si pu usarla per dichiarare una variabile di istanza. Per risolvere lerrore, dopo #import <Foundation/Foundation.h> aggiungiamo #import "Televisore.h" in questo modo la classe Televisore sar conosciuta ed utilizzabile allinterno della classe Telecomando (ricordate? lo abbiamo gi fatto nella classe Televisore a proposito di Elettrodomestico ed in Elettrodomestico a proposito di OggettoFisico). Adesso che abbiamo la variabile che avr il riferimento al televisore, possiamo riprendere la scrittura del codice di accendiIlTelevisore. Sostituiamo [tv1 accendi]; con [myTv accendi]; Quindi ogni volta che invochiamo il metodo accendiIlTelevisore del telecomando, questo non far altro che scrivere una stringa ed invocare il metodo accendi del televisore. Con questa regola vi lascio come esercizio limplementazione degli altri metodi del telecomando. Resta unultima cosa da fare prima di usare il telecomando, bisogna modellare il fatto che nella realt, telecomando e televisore si vedono per poter dialogare, cio il telecomando

aggancia il televisore; noi per ora ci siamo solo creatila variabile di istanza per gestire questo fatto ma non abbiamo ancora costruito nessun legame. Per far questo poich durante lesecuzione dobbiamo modificare una variabile di istanza (myTv) per agganciarla al nostro televisore (tv1) abbiamo bisogno di un metodo che riceva unistanza di Televisore e la assegni a myTv, costruendo cos il collegamento tra televisore e telecomando. Quindi in Telecomando.h aggiungiamo la dichiarazione di un metodo -(void)agganciaIlTelevisore:(Televisore *)ilTelevisore; in Telecomando.m la sua implementazione sar -(void)agganciaIlTelevisore:(Televisore *)ilTelevisore { NSLog(@"Il telecomando sta agganciando il televisore"); myTv=ilTelevisore; } cio, a parte la solita scritta a console, alla variabile di istanza viene assegnato il riferimento alloggetto passato al momento dellinvocazione. A questo punto torniamo al main.m, aggiungiamo in testa #import "Telecomando.h" Poi, dopo la dichiarazione della variabile tv1 aggiungiamo Telecomando *tc1; cio, dichiariamo una variabile di nome tc1 che conterr un riferimento ad unistanza della classe Telecomando. Dopo la creazione delloggetto associato a tv1 tv1=[[Televisore alloc] init]; creiamo anche un oggetto telecomando scrivendo tc1=[[Telecomando alloc] init]; Vi ricordo brevemente il significato di quello che abbiamo scritto:

[Telecomando alloc] crea unistanza della classe Telecomando; [[Telecomando alloc] init] sullistanza appena creata viene invocato il metodo init che restituisce listanza stessa; tc1= listanza restituita da init assegnata alla variabile tc1. Abbiamo adesso i nostri due oggetti, il televisore ed il telecomando; la prima cosa da fare agganciarli luno con laltro (dobbiamo far s che si vedano) quindi, dopo listruzione tc1= aggiungiamo [tc1 agganciaIlTelevisore:tv1]; linvocazione del metodo della classe Telecomando con il quale assegnamo alla variabile di istanza myTv il riferimento al televisore tv1. A questo punto possiamo iniziare ad usare il nostro telecomando, quindi cancelliamo tutte le istruzioni che erano invocazioni di metodi sul televisore e sostituiamole con [tc1 accendiIlTelevisore]; [tc1 mostraIlCanaleDelTelevisore]; [tc1 alzaIlVolumeDelTelevisore]; [tc1 alzaIlVolumeDelTelevisore]; [tc1 alzaIlVolumeDelTelevisore]; [tc1 abbassaIlVolumeDelTelevisore]; [tc1 abbassaIlVolumeDelTelevisore]; [tc1 abbassaIlVolumeDelTelevisore]; [tc1 vaiAlCanale:[[NSDecimalNumber alloc] initWithString:@"3"]]; [tc1 mostraIlCanaleDelTelevisore]; [tc1 spegniIlTelevisore]; o con la sequenza di comandi che preferite. Bene, ci fermiamo qui e direi che le basi della programmazione OO le abbiamo viste; il prossimo capitolo lo dedichiamo ad una overview su un aspetto importante che sono le properties e poi ci sposteremo su Cocoa x iOS, cio sempre sfruttando televisori e

telecomandi andremo ad analizzare i principali framework per lo sviluppo su iPhone e avremo anche modo di rivedere ed approfondire tutti i concetti visti finora.

13 Propriet
iOS, Mac, OSX, SDK, Tech Room

La spiegazione di cosa una propriet (property) partir dal loro uso pi comune (a mio avviso anche il pi fuorviante) e poi per passi successivi arriveremo alla forma generale. Riprendiamo il nostro programma e prendiamo in considerazione la variabile di istanza canaleSintonizzato. Scriviamo due metodi, uno che restituisce il valore della variabile e uno che lo scrive (in realt questultimo lo abbiamo gi e si chiama vaiAlCanale ma per scopi didattici ne scriviamo un altro). Andiamo nel file Televisore.h e dichiariamo i due metodi

-(NSDecimalNumber *)canaleSintonizzato; questa la dichiarazione di un metodo di istanza ( preceduto dal segno -) che ha lo stesso nome della variabile di istanza (canaleSintonizzato) e che restituisce un valore dello stesso tipo della variabile di istanza (NSDecimalNumber *). -(void)setCanaleSintonizzato:(NSDecimalNumber *)canaleDaSintonizzare; questa la dichiarazione di un metodo di istanza ( preceduto dal segno -) che ha lo stesso nome della variabile di istanza (canaleSintonizzato) con la prima lettera in maiuscolo e preceduto da set; il metodo riceve in ingresso al momento dellinvocazione un valore dello stesso tipo della variabile di istanza (NSDecimaNumber *) che sar conosciuto, allinterno del metodo con il nome canaleDaSintonizzare e che sar il nuovo valore che vogliamo assegnare alla variabile di istanza (come ho gi detto avr la stessa logica di vaiAlCanale). Ora andiamo ad implementare questi metodi in Televisore.m. Iniziamo con laggiungere il metodo di lettura -(NSDecimalNumber *)canaleSintonizzato { NSLog (@Esecuzione del metodo di lettura); return canaleSintonizzato; } Vi ricordo che limplementazione di un metodo costituita dallintestazione del metodo stesso (-(NSDecimalNumber *)canale Sintonizzato) seguita dal blocco di istruzioni ({..}) che devono essere eseguite nel metodo. Listruzione return labbiamo gi incontrata e serve per uscire da un metodo restituendo un valore a chi lo ha invocato, quindi questo metodo non fa altro che restituire loggetto referenziato dalla variabile di istanza canaleSintonizzato, cos come richiesto. Passiamo al metodo di scrittura ed aggiungiamo in Televisore.m -(void)setCanaleSintonizzato:(NSDecimalNumber *)canaleDaSintonizzare {

NSLog (@Esecuzione del metodo di scrittura); canaleSintonizzato= canaleDaSintonizzare; } E un metodo di istanza (c il -) che non restituisce alcun valore (il tipo restituito void) che riceve in ingresso unistanza di NSDecimalNumber. Lunica cosa che fa assegnare alla variabile di istanza canaleSintonizzato loggetto ricevuto al momento dellinvocazione canaleDaSintonizzare. Bene ora proviamo ad usare questi metodi. Andiamo nel file Telecomando.m ed andiamo a modificare il metodo -(void)vaiAlCanale:(NSDecimalNumber *)canaleDaSintonizzare{ NSLog(@"Il telecomando sta cambiando il canale"); NSLog(@"Sei passato dal canale %@",[myTv canaleSintonizzato]); [myTv setCanaleSintonizzato:canaleDaSintonizzare]; NSLog(@"al canale %@",[myTv canaleSintonizzato]); } Cio, dopo la prima istruzione che scrive un messaggio generico per individuare il metodo, abbiamo listruzione NSLog(@"Sei passato dal canale %@",[myTv canaleSintonizzato]); la format string ha un segnaposto (%@) che durante lesecuzione sar sostituito dal risultato dellinvocazione di [myTv canaleSintonizzato]. Questo proprio il metodo di lettura che abbiamo appena definito in Televisore.m e che restituisce il valore della variabile canaleSintonizzato. Quindi questa istruzione scriver il messaggio Sei passato dal canale con il numero del canale al posto dei Poi abbiamo listruzione [myTv setCanaleSintonizzato:canaleDaSintonizzare]; che linvocazione del metodo di scrittura che abbiamo appena definito nella classe Televisore al quale passiamo loggetto che

rappresenta il numero del nuovo canale da sintonizzare. Infine, per verificare che cambiato il canale NSLog(@"al canale %@",[myTv canaleSintonizzato]); anche qui la format string ha un segnaposto (%@) che durante lesecuzione sar sostituito dal risultato dellinvocazione di [myTv canaleSintonizzato] cio dal risultato del metodo di lettura e che restituisce il valore della variabile canaleSintonizzato. Quindi questa istruzione scriver il messaggio al canale con il numero del canale al posto di . Proviamo ad eseguire il programma ed osserviamo i messaggi. In particolare troverete la sequenza 2011-06-10 11:44:16.649 PrimoProgetto[98830:903] Il telecomando sta cambiando il canale del metodo vaiAlCanale di Telecomando 2011-06-10 11:44:16.650 PrimoProgetto[98830:903] Esecuzione del metodo di lettura del metodo getter della property canaleSintonizzato invocato dallistruzione NSLog(@Sei passato dal canale %@,[myTv canaleSintonizzato]); 2011-06-10 11:44:16.651 PrimoProgetto[98830:903] Sei passato dal canale 0 del metodo vaiAlCanale di Telecomando 2011-06-10 11:44:16.664 PrimoProgetto[98830:903] Esecuzione del metodo di scrittura del metodo setter della property canaleSintonizzato invocato dallistruzione [myTv setCanaleSintonizzato:canaleDaSintonizzare]; 2011-06-10 11:44:16.665 PrimoProgetto[98830:903] Esecuzione del metodo di lettura del metodo getter della property canaleSintonizzato invocato dallistruzione NSLog(@al canale %@,[myTv canaleSintonizzato]); 2011-06-10 11:44:16.667 PrimoProgetto[98830:903] al canale 3

del metodo vaiAlCanale di Telecomando funziona tutto come previsto ma non dovevamo parlare di propriet? Finora abbiamo solo scritto ed usato altri due metodi, nulla di nuovo rispetto a quanto avevamo gi visto. Questa situazione, cio una coppia di metodi uno che legge un valore e laltro che lo scrive, proprio il concetto base delle propriet e la coppia di metodi, chiamata metodi di accesso, costituita dal metodo getter (quello che legge) e dal metodo setter (quello che scrive). La definizione generale di una property @property (attributi) tipoPropriet nomePropriet; Trascuriamo per il momento cosa sono gli attributi tra le ( ). La dichiarazione precedente implicitamente dichiara una coppia di metodi di accesso getter: -(tipoPropriet)nomePropriet; cio un metodo che ha lo stesso nome della propriet e restituisce un valore dello stesso tipo; setter: -(void)setNomePropriet:(tipoPropriet)valore; cio un metodo che ha il nome della propriet con la prima lettera in maiuscolo e preceduto da set, che prende come argomento un valore del tipo della propriet e che sar il valore da assegnare. Allora cominciamo ad usare la property. Nel file Televisore.h, prima della lista dei metodi, ma fuori dal blocco di dichiarazioni delle variabili di istanza, scriviamo @property (nonatomic, retain) NSDecimalNumber *canaleSintonizzato; In linea con quanto abbiamo appena detto, questa implicitamente comporta la dichiarazione dei metodi getter/setter esattamente uguali a quelli che abbiamo dichiarato noi esplicitamente -(NSDecimalNumber *)canale Sintonizzato; -(void)vaiAlCanale:(NSDecimalNumber *)canaleDaSintonizzare; quindi avendo dichiarato la property, possiamo cancellare le nostre due dichiarazioni dei metodi da Televisore.h. Proviamo ad eseguire il programma e vediamo che funziona tutto come prima.

Un effetto delladozione delle property lintroduzione di una nuova forma sintattica. Vediamola ed analizziamola. In Telecomando.m cambiamo il metodo -(void)vaiAlCanale: (NSDecimalNumber *)canaleDaSintonizzare nel quale abbiamo usato la property canaleSintonizzato del Televisore, listruzione NSLog(@"Sei passato dal canale %@",[myTv canaleSintonizzato]); la sostituiamo con NSLog(@"Sei passato dal canale %@",myTv.canaleSintonizzato); cio invece di scrivere linvocazione di un metodo facciamo seguire alla variabile che referenzia loggetto il nome della property separato da un ., questa notazione si traduce nellinvocazione del metodo getter. Listruzione [myTv setCanaleSintonizzato:canaleDaSintonizzare]; la sostituiamo con myTv.canaleSintonizzato=canaleDaSintonizzare; cio alla nuova notazione istanza.propriet viene assegnato un valore, questo si traduce nellinvocazione del metodo setter a cui viene passato il valore a destra del segno = come parametro. In maniera analoga sostituiamo listruzione NSLog(@"al canale %@",[myTv canaleSintonizzato]); con NSLog(@"al canale %@",myTv.canaleSintonizzato); che invoca di nuovo il metodo getter. Eseguiamo di nuovo il programma e verifichiamo che anche con la nuova notazione sono eseguiti i metodi di accesso. Tuttavia finora abbiamo risparmiato due righe di codice e ne abbiamo inserito una, non un gran vantaggio. Lintroduzione della property in realt ci consente anche di non definire i metodi in Televisore.m, cancelliamo quindi la definizione dei metodi da Televisore.m, ma dopo la riga @implementation Televisore

aggiungiamo @synthesize canaleSintonizzato; listruzione @synthesize propriet non fa altro che dire al compilatore di predisporre lui i metodi standard getter/setter; quindi non abbiamo pi bisogno di scriverli noi. Ora in main.m invochiamo pi volte il metodo per cambiare il canale e eseguendo il programma verifichiamo che i messaggi a console siano corretti, ovviamente non ci sono pi i messaggi che avevamo scritto nei nostri getter/setter ma il funzionamento corretto. Bene questo luso principale che si fa delle property: si dichiara una property con lo stesso tipo e nome di una variabile di istanza si inserisce listruzione synthesize della property si ottengono automaticamente due metodi per leggere e scrivere il valore della variabile di istanza accessibili attraverso la notazione (dot notation) istanza.propriet. Fissati questi punti e se non sono chiari vi invito a ripercorrere il capitolo, passiamo a vedere la prima variante. Cosa succede se la property non ha lo stesso nome di una variabile di istanza? Andiamo in Televisore.h e modifichiamo la dichiarazione della property in @property (nonatomic, retain) NSNumber *canale; una propriet che non ha il nome di alcuna variabile di istanza (noi abbiamo canaleSintonizzato) ed di un tipo NSNumber che non abbiamo ancora incontrato. Approfondimento: andate sulla documentazione Apple relativa a NSDecimalNumber ed osservate la sua gerarchia. Poi andiamo in Televisore.m e modifichiamo listruzione synthesize in @synthesize canale=canaleSintonizzato; cio diciamo che la property canale usa la variabile di istanza canaleSintonizzato. A questo punto andiamo nei metodi

mostraIlCanaleDelTelevisore e vaiAlCanale di Telecomando.m dove abbiamo usato la precedente property e scriviamoci il nuovo nome, cio dove abbiamo usato myTv.canaleSintonizzato ora useremo myTv.canale. Eseguiamo ed osserviamo i messaggi; tutto uguale, quindi possiamo anche usare propriet con un nome diverso da quello delle variabili di istanza e poi al momento del synthesize dire quale variabile si usa. Quindi una property deve sempre essere riferita ad una variabile di istanza? Torniamo in Televisore.h ed aggiungiamo la dichiarazione @property (readonly) NSString *marca; abbiamo dichiarato una property di nome marca e che si riferisce ad istanze di NSString. La classe NSString labbiamo gi incontrata ed quella che, in Cocoa gestisce le stringhe (sequenze) di caratteri, cio parole o frasi. Lattributo readonly indica che si tratta di una propriet in sola lettura e questo vuol dire che come metodo di accesso avr solo il getter. Andiamo in Televisore.m e questa volta non facciamo la synthesize ma definiamo noi il metodo getter coma abbiamo fatto allinizio del capitolo, quindi aggiungiamo -(NSString *)marca { return @"Apple"; } il tipico metodo di accesso getter, ha lo stesso nome della property e restituisce un oggetto dello stesso tipo. In particolare il nostro metodo restituir sempre la stringa Apple, tutti i nostri televisori hanno marca Apple. Per vedere leffetto modifichiamo il metodo mostraInfo di Televisore -(void)mostraInfo { NSLog(@"Il televisore %@ pesa %@ Kg e consuma %@ W",self.marca,peso,consumo); } Cio abbiamo aggiunto un nuovo segnaposto dopo la parola televisore e in corrispondenza abbiamo inserito un nuovo

parametro il cui valore sar visualizzato al suo posto, self.marca; questa forma quella che abbiamo gi incontrato e corrisponde allinvocazione del metodo getter della property marca. Eseguiamo ed osserviamo i messaggi, potremo vedere che il nuovo messaggio di informazioni sar 2011-06-10 15:12:55.915 PrimoProgetto[39487:903] Il televisore Apple pesa 20 Kg e consuma 500 W comparso il valore della property. Quindi.abbiamo definito una property non associata ad alcuna variabile di istanza. Per semplicit didattica in questo momento abbiamo solo una property readonly. Questo il concetto finale al quale volevo arrivare. Una property implica sempre una coppia di metodi getter/setter (se readonly solo getter) che pu o meno essere associata ad una variabile di istanza anche se questo luso pi diffuso. Come esercizio vi invito a riscrivere tutto il programma togliendo tutte le variabili di istanza e sostituendole tutte con property. Dopo averci provato potete scaricare qui il mio codice.

14 Model-View-
iOS, OSX, SDK, Tech Room

Abbiamo detto pi volte ed abbiamo anche visto con il nostro esempio, che unapplicazione Objective-C costituita da un insieme di oggetti cooperanti (tv1, tc1) definiti tramite classi (Televisore, Telecomando, Elettrodomestico,). La realizzazione di unapplicazione complessa in iOS prevede la cooperazione di un certo numero di oggetti e di diverse classi, per cui per rendere pi semplice lo sviluppo e i successivi aggiornamenti del programma necessario trovare un modo per organizzare gli oggetti. Il modo con il quale organizzare gli oggetti

di unapplicazione prende il nome di paradigma o pattern ed il principale il Model-View-Controller (MVC). In questo pattern gli oggetti sono raggruppati (solo logicamente) in Model: il gruppo degli oggetti che rappresentano il contesto della nostra applicazione (televisori, telecomandi,) View: il gruppo degli oggetti che costituiscono linterfaccia utente (viste, bottoni,) Controller: il gruppo degli oggetti che collegano linterfaccia al modello implementando la logica elaborativa e gestendo gli eventi. Vediamo cosa significa in pratica. Creiamo un nuovo progetto, lanciamo XCode e, nella finestra dei template selezioniamo la voce Application del gruppo iOS; qui vediamo diversi schemi di applicazione gi predisposti da Apple e che potrete approfondire nella documentazione, noi per motivi didattici scegliamo il template base Window-based Application

proseguiamo, diamo un nome al progetto (es. myTv) e selezioniamo iPhone come target, scegliamo la directory e creiamo il progetto. Potete vedere che anche stavolta XCode ha gi preparato dei file, li analizzeremo man mano che ne avremo bisogno. Model Tutto il lavoro fatto fino al cap 13 con il nostro programma precedente possiamo dire che coincide con la definizione del modello (il livello Model), cio abbiamo creato le classi che modellano il nostro contesto fatto di televisori e telecomandi; sia in termini di propriet che di comportamenti. Non abbiamo quindi molto da dire se non che useremo le classi che abbiamo disegnato nel progetto gi fatto cos come le abbiamo lasciate alla fine del capitolo precedente, cio con lutilizzo delle propriet e senza variabili di istanza esplicite. Quello di cui abbiamo quindi bisogno di portarci in questo

nuovo progetto le classi che abbiamo gi definito. Con il clic destro del mouse sulla cartella myTv nellalbero di XCode selezioniamo add Files to myTv , nella finestra che ci viene presentata navighiamo il disco fino a trovare la cartella del nostro vecchio progetto (primoProgetto) e selezioniamo i file relativi alle classi OggettoFisico, Elettrodomestico, Televisore, sia i .h che i .m, assicuriamoci che sia attivo il flag Destination. Dal menu di XCode selezioniamo Product>Build in modo da compilare lapplicazione per assicurarci che abbiamo importato correttamente i file. Bene, il Model pronto. View Nella componente View dobbiamo innanzitutto disegnare la nostra interfaccia per capire di quali elementi grafici abbiamo bisogno. Decidiamo che il nostro programma visualizza un televisore come quello rappresentato in figura.

Quindi di quali elementi grafici abbiamo bisogno? A. Un contenitore generale dei controlli dellinterfaccia, occupa tutto lo spazio dello schermo delliPhone; B. Un riquadro grigio che simuler lo schermo del televisore; lo schermo diventer nero quando il televisore spento; C. Un testo dentro lo schermo che indicher il canale sintonizzato; D. Un controllo a scorrimento per la regolazione del volume; E. Un testo che indicher il livello del volume F. Un selettore a 5 tasti per selezionare i canali del televisore G. Un bottone di tipo interruttore per accendere e spegnere il televisore Passiamo ora ad implementare questa struttura in XCode. Per creare una schermata di interfaccia grafica, come quella che abbiamo disegnato, dobbiamo creare un altro file; con il clic destro sulla cartella myTv scegliamo New File e nel gruppo iOS selezioniamo User Interface e scegliamo il template View ( quella

che possiamo approssimare ad una schermata) e chiamiamola TelevisoreView. XCode crea un file dedicato alla creazione grafica dellinterfaccia chiamato TelevisoreView.xib. Finita la creazione XCode vi presenta una vista vuota (bianca) con le proporzioni delliPhone, questa prima View lelemento A del nostro disegno. Nella toolbar della finestra di XCode selezioniamo licona frame destro nel gruppo View; questo frame contiene 2 parti: quella superiore linspector con il quale si configurano gli attributi dellelemento di interfaccia selezionato (come vedremo in seguito); quella inferiore la libreria degli oggetti utilizzabili.

Andiamo ora a popolare la nostra interfaccia. Lelemento B, un riquadro che simula lo schermo e che cambier colore da nero a grigio a seconda che il televisore sia spento o acceso. Per creare questo elemento di interfaccia usiamo una View, quindi scorriamo la Library (parte inferiore del frame di destra) e cerchiamo una View; la trasciniamo su quella esistente e lasciamo il mouse. Con la nuova view selezionata andiamo nel pannello Attributes dellInspector (parte superiore del frame di destra) e alla voce background cambiamo il colore da bianco a grigio chiaro. Ora per dobbiamo dire che lo schermo non deve occupare tutta la view sottostante, quindi ridimensioniamola o con i connettori che trovate intorno alla view stessa quando selezionata, oppure andando nel pannello Size dellInspector. Passiamo allelemento C, il testo che mostrer il nome del canale dentro lo schermo.

Per modellare questo elemento useremo una Label. Prendiamo quindi una Label dalla Library e trasciniamola dentro lo schermo. Passiamo allelemento D, il regolatore di volume. Usiamo un controllo di tipo Slider, quindi prendiamolo dalla Library e posizioniamolo sotto lo schermo e diamogli una dimensione adeguata rispetto a come abbiamo disegnato la nostra interfaccia. Se ricordate, nel nostro modello il volume ha un valore massimo di 100 ed un valore di default di 50. Quindi con lo slider che abbiamo inserito selezionato, andiamo nel pannello Attributes e cambiamo i valori Maximum e Current mettendoci 100 e 50. Passiamo allelemento E, il testo con il valore corrente del volume. Abbiamo bisogno di unaltra Label, quindi la prendiamo dalla Library e la posizioniamo a fianco allo slider. Passiamo allelemento F, il selettore dei canali. Per questo tipo di controlli si usa un Segmented Control; prendiamolo dalla Library e posizioniamolo nella nostra interfaccia. Con questo controllo selezionato, andiamo nel pannello Attributes e cambiamo il valore di Segments da 2 a 5. Adesso facciamo doppio clic su ognuno dei pulsanti del controllo e scriviamoci i numeri da 1 a 5; se durante questa operazione il controllo dovesse ridimensionarsi risistemiamo tutto o con i connettori sui bordi o con i valori del pannello Size dellInspector. Infine aggiungiamo lelemento G, linterruttore. Questo tipo di controllo ben modellato da uno Switch, quindi prendiamolo dalla Library e trasciniamolo nella posizione desiderata. Nel pannello Attributes cambiamo State in Off (linterruttore spento). Alla fine dovremmo avere qualcosa del genere

Bene analizziamo un po meglio cosa abbiamo fatto. Abbiamo preso degli elementi grafici di interfaccia e li abbiamo sistemati come desideravamo. Questi elementi non solo solo delle immagini ma sono dei veri e propri oggetti di Objective-C. Se sono oggetti vuol dire che hanno una classe che li caratterizza. Selezioniamo la View che abbiamo usato per disegnare lo schermo e poi andiamo sul tab Quick Help dellInspector, qui ci viene detto a quale classe appartiene loggetto (UIView) con una breve descrizione della classe. Inoltre alla voce Reference abbiamo un link alla documentazione dove compiutamente descritta la classe e dove sono elencati e descritti tutti i metodi e propriet delle istanze della classe. Approfondimento: selezionare gli elementi di interfaccia uno alla volta e studiare la documentazione della classe, in particolare osservate a quale superclasse appartiene ognuna di esse. Unaltra considerazione da fare, parte dallosservare il frame Objects che trovate a sinistra della nostra interfaccia. Potete

vedere tutti gli elementi di interfaccia con una struttura gerarchica che rappresenta la relazione di contenimento. C una View (il nostro elemento A) che contiene tutti gli altri elementi, tra cui la View dello schermo che a sua volta contiene la Label del nome del canale. Questa gerarchia di oggetti, superview/subview, un meccanismo importante da conoscere per poter gestire al meglio le interfacce via codice. Notiamo che la gerarchia tra gli oggetti (contenimento) non ha alcuna relazione con la gerarchia tra le classi (specializzazione). Nel prossimo capitolo vedremo come creare il controller per correlare il modello con linterfaccia. 15 - Controller
iOS, OSX, SDK, Tech Room

Riprendiamo il nostro progetto myTv. Abbiamo da un lato Model, definito dalle nostre classi Televisore, Telecomando, dallaltro lato abbiamo View, costituito dalla nostra Interfaccia Utente e da tutti i suoi oggetti (bottoni, viste, label,). Adesso dobbiamo collegarli insieme; per fare questo abbiamo bisogno di un controller. Cosa un controller? Siamo in un ambiente Object-Oriented quindi al risposta facile: un oggetto. La sua caratteristica di poter vedere sia gli oggetti del modello, quindi di leggere e scrivere le loro propriet, che quelli dellinterfaccia Utente. Con questi ultimi il controller stabilisce due modalit di relazione dalla UI verso il controller (Action): in risposta ad eventi che intervengono sullinterfaccia (bottoni premuti, touch sulle viste,) si invocano particolari metodi del controller dal controller verso la UI (Outlet): dal controller posso leggere e scrivere propriet degli oggetti della UI (il colore di una

vista, lo stato di un bottone,) o eseguire i metodi che espongono Con gli oggetti del modello invece avr una sola relazione, cio sar in grado di invocare i loro metodi o di leggere/scrivere le propriet

con il termine send/target si indica il pattern di programmazione per il quale un oggetto invia un messaggio ad un altro oggetto per richiedere lesecuzione di un metodo; nulla di diverso da quanto abbiamo fatto finora ad esempio tra il telecomando tc1 e il televisore tv1, dalloggetto tc1 abbiamo invocato i metodi di tv1; questo schema si chiama send/target. Nel disegno precedente abbiamo lasciato una linea tratteggiata, quando sar il momento vedremo che pattern si usa per quel tipo di comunicazione. Andiamo ora a creare il nostro oggetto controller. Per creare un oggetto in un ambiente OO di cosa abbiamo bisogno? Della definizione della sua classe. Per loggetto controller dobbiamo creare una nuova classe in quanto qualcosa di diverso dai tipi di oggetto visti finora (Televisori, telecomandi,). Andiamo sul nostro progetto in XCode e con il click destro sulla cartella myTv scegliamo New File. Nel gruppo iOS selezioniamo

Cocoa Touch e scegliamo il template UIViewController. Nel pannello successivo lasciamo come subclass UIViewController e togliamo i flag Targeted for iPad, visto che noi stiamo sviluppando per iOS, e with XIB for user interface, visto che noi il file con al user interface lo abbiamo gi creato. Diamogli il nome TVController e salviamo. Se andiamo a guardare il file TVController.h vediamo che ci stata creata la classe come sottoclasse di UIViewController, questa una classe predefinita in Cocoa ed quella che si usa quando la root della nostra interfaccia un UIView, come nel nostro caso; se riapriamo il file TelevisoreView.xib vediamo che loggetto che contiene tutti gli altri unistanza di UIView. Torniamo al nostro TVController.h; qui dobbiamo dichiarare tutte le property ed i metodi che ci servono per far parlare loggetto controller con il modello e con linterfaccia. Andiamo adesso a dichiarare i flussi con linterfaccia. Iniziamo con quelli che vanno dallinterfaccia verso il controller, quelli che abbiamo chiamato Action. Questi flussi si implementano dichiarando dei metodi che saranno invocati in risposta alle nostre interazioni con linterfaccia. La prima interazione quella con linterruttore, dovremo avere un metodo che viene invocato quando accendiamo o spegnamo il televisore. Prima della riga @end scriviamo -(IBAction)accendiSpegni:(id)sender; questa una dichiarazione di un metodo, ne abbiamo viste tante nei capitoli precedenti; un metodo di istanza (inizia con il -), il tipo del risultato IBAction, si tratta di unindicazione particolare nel senso che dal punto di vista linguistico il tipo restituito void, cio il metodo non restituisce nulla a chi lo ha invocato, ma, usando IBAction il controller potr usare questo metod in risposta alle nostre interazioni con linterfaccia. Poi c il nome che abbiamo scelto, accendiSpegni; infine c il parametro che il

metodo riceve quando viene invocato, (id)sender, tutti i metodi che devono rispondere ad eventi di interfaccia ricevono un parametro che sar valorizzato con loggetto che ha generato levento, nel nostro caso sar loggetto interruttore. Lindicazione (id)ender vi ricordo che vuol dire che il metodo potr usare al suo interno il nome sender per riferirsi allargomento associato e che questo argomento sar un oggetto di tipo id. Il tipo id indica un qualunque tipo , potr essere un bottone, uno slider, o un qualunque altro oggetto. Andiamo ora da implementare questo metodo. Andiamo nel file TVController.m e prima di @end scriviamo -(IBAction)accendiSpegni:(id)sender { NSLog(@"Metodo accendi/spegni"); } Abbiamo cio definito il metodo dichiarato nel header (il file .h) e per ora quello che facciamo solo fargli scrivere una stringa. Ora ci resta da legare questo metodo allevento dellinterruttore. Andiamo nel file TelevisoreView.xib (quello dove c disegnata la nostra interfaccia); nel pannello di sinistra c una sezione placeholders allinterno della qual e trovate un oggetto che si chiama Files owner, bene quello il segnaposto che a runtime sar il nostro controller, ora dobbiamo solo dirgli di quale classe sar il nostro controller. Selezioniamo il Files owner ed andiamo nel pannello Identity Inspector (ricordatevi di aprire i pannelli destro e sinistro se non lo sono gi), nel men a tendina del campo Class selezionate TVController. In questo modo abbiamo detto che loggetto controller apparterr alla nostra classe. La prima cosa che dobbiamo fare dire al controller qual la vista che deve mostrare nelliPhone. Per fare questo clicchiamo con il bottone destro sul Files owner, appare una finestra con le propriet ed i metodi del controller, individuate la property view e cliccando sul cerchietto corrispondente, trascinate il mouse sopra la view principale dellinterfaccia (fate attenzione a non

agganciarla invece ad uno degli oggetti contenuti). Ora con il bottone destro premuto trasciniamo il mouse dalloggetto interruttore a Files owner, vi verr presentata una finestra con il metodo accendiSpegni che selezionerete. In questo modo abbiamo detto che in risposta al cambio di stato dellinterruttore verr invocato il nostro metodo. Bene, ora lultima cosa che dobbiamo fare dire al sistema che questa linterfaccia da caricare allinizio. Aprite il file MainWindow.xib, qui trovate loggetto Window, questo oggetto rappresenta la finestra di iPhone nella quale si alternano le diverse view dellinterfaccia, la Window ha una property rootViewController che deve esser agganciato al viewController che gestisce la view da mostrare. Procediamo in questo modo: nella Library degli oggetti (la sezione inferiore del pannello di destra) cercate loggetto ViewController e trascinatelo nella sezione Objects. Selezionate loggetto appena inserito e nellIdentity Inspector nel campo Class scegliete TVController. NellAttributes Inspector nel campo NibName scrivete TelevisoreView, in questo modo gli diciamo che questo viewController caricher il nostro file xib dellinterfaccia. Infine cliccate con il bottone destro sulloggetto Window e nella finestra che si apre trascinate il mouse con il bottone sinistro dal cerchietto di rooViewController sulloggetto viewController che abbiamo appena inserito. Siamo ora pronti per provare linterruttore, clicchiamo su Run e vediamo che parte il simulatore di iPhone presentandoci linterfaccia che abbiamo preparato. Spostiamo linterruttore ed osserviamo la console dove dovrebbe apparire il messaggio che abbiamo scritto nel metodo accendiSpegni. Riepiloghiamo i punti che abbiamo visto:

Abbiamo creato la classe del nostro controller, ci dichiareremo dentro gli oggetti di interfaccia e di modello da collegare; nel file xib della nostra interfaccia abbiamo associato questa classe al segnaposto Files owner, a run time questo segnaposto rappresenter il nostro oggetto controller; abbiamo legato la property view del controller alla view contenitore della nostra interfaccia; abbiamo legato il bottone al metodo accendiSpegni di Files owner, in tal modo quando agiremo sullinterruttore sar invocato il metodo accendiSpegni; in MainWindow.xib abbiamo inserito on oggetto ViewController abbiamo assegnato la classe di questo oggetto alla nostra classe controller abbiamo definito che il controller carica il nostro xib abbiamo legato la property rootViewController delloggetto Window al viewController appena inserito; in tal modo allavvio dellapplicazione sar visualizzata la nostra interfaccia; Il passo successivo che facciamo vedere leffetto di accendere e spegnere sulla view che usiamo come schermo della tv. Cio faremo s che quando accendiamo lo schermo diventi grigio chiaro e quando spegniamo diventi nero. Per fare questo dovremo chiedere alla view di cambiare colore abbiamo quindi bisogno di un riferimento alla view. Prima di procedere per dobbiamo rendere coerente la nostra interfaccia; in questo momento abbiamo linterruttore su Off e lo schermo grigio chiaro come se fosse acceso; sistemiamo le cose facendo diventare nero lo schermo. Apriamo il file TelevisoreView.xib, selezioniamo la view che ci fa da schermo, quella grigia che contiene il nome del canale, nel pannello Attibutes dellInspector selzioniamo Black Color nel menu relativo a Background.

Riprendiamo ora con il nostro codice. Nel file TVController.h, dopo la } scriviamo @property (nonatomic, retain) IBOutlet UIView *schermo; cio abbiamo dichiarato una property di nome schermo che si riferir ad istanze di UIView; rispetto alla dichiarazione solita delle property abbiamo aggiunto la keyword IBOutlet, questo fa s che questa property si possa riferire ad oggetti dellinterfaccia grafica. Vediamo come. Torniamo sul file TelevisoreView.xib e clicchiamo con il destro sul segnaposto Files owner, nella finestra che appare clicchiamo sul cerchietto della property schermo e trasciniamo il mouse sulla view grigia (quella che usiamo per lo schermo); in questo modo abbiamo collegato la nostra property del codice con un elemento di interfaccia. Ora nel codice usando la variabile schermo possiamo agire sulloggetto di interfaccia. Andiamo nel file TVController.m e la prima cosa da fare avendo dichiarato una property quella di far definire i metodi di accesso (getter/setter), per far questo dopo @implementation TVController scriviamo @synthesize schermo; come abbiamo visto nel capitolo precedente, con questa istruzione il compilatore prepara i metodi di accesso alla property (il getter schermo ed il setter setSchermo:). Ora nel metodo accendiSpegni, dopo listruzione NSLog() scriviamo schermo.backgroundColor=[UIColor lightGrayColor]; schermo.backgroundColor la dot notation per accedere alla property backgroundColor, quindi con questa istruzione assegnamo un valore alla property.

Approfondimento: Guardate sulla documentazione la classe UIView e la sua property backgroundColor. Che valore assegnamo a backgrounColor? Il risultato di [UIColor lightGrayColor]. Questa linvocazione di un metodo di classe predefinito, blackColor, sulla classe UIColor; questo metodo restituisce unistanza di UIColor che rappresenta il colore grigio chiaro. Approfondimento: Guardate sulla documentazione la classe UIColor ed i metodi per restituire i colori. Abbiamo fatto un passo avanti, ora il nostro televisore si accenda, ma non si spegne; in effetti nel nostro metodo noi assegnamo sempre il colore grigio chiaro, senza sapere se stiamo accendendolo o spegnendolo. Dobbiamo cio implementare qualcosa del tipo se il televisore acceso spegnilo altrimenti accendilo; questa unistruzione condizionale, labbiamo gi incontrata, che esegue blocchi diversi di codice a seconda del valore di una determinata condizione. La prima cosa che dobbiamo definire come traduciamo in codice la verifica dello stato del televisore (se acceso o spento); si possono fare diverse scelte, quella che andremo ad implementare noi quella di avere una variabile di istanza nella quale manteniamo lo stato del televisore. In TVController.h allinterno delle {} scriviamo BOOL acceso; cio abbiamo dichiarato una variabile di nome acceso e di tipo BOOL. Il tipo BOOL predefinito in Cocoa e rappresenta i valori logici Vero o Falso; non si tratta di una classe e pertanto non c bisogno del * prima del suo nome. Ora dobbiamo dare un valore iniziale a questa variabile coerente

con lo stato iniziale del nostro televisore e cio Falso. Ma dove lo inizializziamo questo valore? Ricordate quando abbiamo lavorato con i nostro oggetti tv1, tc1? Avevamo un metodo init della classe che veniva eseguito quando loggetto veniva creato; bene ne abbiamo anche nel nostro controller leggermente pi complesso ma con lo stesso comportamento e si chiama initWithNibName:bundle: quindi, in TVController.m andiamo allinterno di questo metodo tra le {} dopo if(self) scriviamo acceso=FALSE; A questo punto possiamo cambiare il metodo accendiSpegni, come segue -(IBAction)accendiSpegni:(id)sender { NSLog(@"Metodo accendi/spegni"); acceso=!acceso; if (acceso) { schermo.backgroundColor=[UIColor lightGrayColor]; } else { schermo.backgroundColor=[UIColor blackColor]; } } La prima istruzione (dopo NSLog) acceso=!acceso; un istruzione di assegnamento che assegna ad acceso il risultato di !acceso. Loperatore ! un operatore che agisce su un valore logico (Vero/Falso) e ne restituisce la sua negazione. Cio se acceso Falso restituisce Vero mentre se acceso Vero restituisce Falso. Quindi dopo questa istruzione accesso conterr il contrario di quello che conteneva prima (se il televisore era spento ora acceso e se era acceso ora spento). Listruzione condizionale si implementa con if (<espressione vero/falso>) { <blocco Vero>

} else { <blocco Falso> } cio si stabilisce una espressione che restituisce Vero o Falso e a seconda del risultato della sua valutazione si esegue il primo o il secondo blocco. Quindi nel nostro caso il blocco Vero semplicemente listruzione che fa diventare grigio lo schermo ed il blocco Falso quello che lo fa diventare nero. Mentre la condizione semplicemente basata sullo stato della variabile acceso. Bene, a questo punto andiamo a definire gli altri collegamenti con linterfaccia. Prima cosa individuiamo quelli che richiedono unaction cio i controlli che devono comunicare con il controller invocandone dei metodi. In questa categoria ci sono lo slider per regolare il volume e la pulsantiera per il cambio dei canali. Poi ci sono i controlli che invece devono ricevere uninvocazione dal controller, e che quindi avranno bisogno di Outlet; questi sono la label dentro lo schermo che indicher il nome del canale e la label vicino allo slider del volume che invece indicher il livello del volume. Andiamo quindi in TVController.h e dichiariamo i metodi (regolaVolume e cambiaCanale) e le propriet (nomeCanale e livelloVolume) di cui abbiamo bisogno. Alla fine dovreste avere #import <UIKit/UIKit.h> @interface TVController : UIViewController { } @property (nonatomic, retain) IBOutlet UIView *schermo; @property (nonatomic, retain) IBOutlet UILabel *nomeCanale; @property (nonatomic, retain) IBOutlet UILabel *livelloVolume;

-(IBAction)accendiSpegni:(id)sender; -(IBAction)regolaVolume:(UISlider *)sender; -(IBAction)cambiaCanale:(UISegmentedControl *)sender; @end Ora andiamo in TelevisoreView.xib e facciamo i collegamenti tra il controller e gli elementi dellinterfaccia grafica. Quindi con il bottone destro premuto trasciniamo il mouse dallo slider su Files owner e selezioniamo il metodo regolaVolume, facciamo lo stesso con il selettore dei canali scegliendo il metodo cambiaCanale. Andiamo ora ad implementare questi ultimi metodi nel nostro controller. In TVController.m per prima cosa dobbiamo fare i synthesize per le property che abbiamo dichiarato, quindi aggiungiamo al syntesize gi presente @synthesize nomeCanale; @synthesize livelloVolume; Dopo il metodo accendiSpegni aggiungiamo -(IBAction)regolaVolume:(UISlider *)sender { if (acceso) { NSLog(@"Il volume a livello %f", sender.value); livelloVolume.text=[NSString stringWithFormat:@"%f",sender.value]; } } questo il metodo che sar invocato quando lo slider del volume cambia posizione. La prima cosa che facciamo controllare se il televisore acceso (la variabile acceso vale Vero), altrimenti non facciamo nulla. Iniziamo con il farci scrivere un messaggio a console. La stringa che usiamo una format string (ormai dovreste sapere cosa ) cio una stringa con un segnaposto %f. Questo segnaposto indica che a

runtime sar sostituito da un numero frazionario. Il valore che sar scritto al posto di %f il risultato di sender.value questo laccesso ad una property value delloggetto sender. E una property predefinita per la classe UISlider, cui appartiene sender, che restituisce il valore corrispondente alla posizione dello slider. Approfondimento: Guardate sulla documentazione Apple la classe UISlider e la property value. Laltra cosa che dobbiamo fare aggiornare la label a fianco allo slider che ci indica il livello del volume; Questo lo otteniamo con livelloVolume.text=[NSString stringWithFormat:@"%f",sender.value]; un assegnamento a livelloVolume.text; questo laccesso alla property text, predefinita per UILabel, che prende unistanza di NSString come valore. Approfondimento: Guardate sulla documentazione Apple la classe UILabel e la property text. Noi assegnamo il risultato di [NSString stringWithFormat:@"%f",sender.value] questa linvocazione di un metodo di classe di NSString, stringWithFormat:, che prende in input una format string ed i suoi parametri e restituisce listanza di NSString che la rappresenta. Approfondimento: Guardate sulla documentazione Apple la classe NSString ed i metodi di classe stringWith. Aggiungiamo lultimo metodo -(IBAction)cambiaCanale:(UISegmentedControl *)sender { if (acceso) { NSLog(@"Il canale selezionato il numero %d",sender.selectedSegmentIndex); } }

questo il metodo che sar invocato quando selezioniamo un bottone del selettore dei canali ed in questo momento no fa altro che scrivere un messaggio (solo se acceso Vero) con al solita format string con un segnaposto questa volta %d, cio a runtime sar sostituito da un numero intero. In particolare sar sostituito da sender.selectedSegmentIndex , questo laccesso alla property selectedSegmentIndex delloggetto sender. E una property predefinita per la classe UISegmentedControl di cui sender istanza, che restituisce il numero del pulsante premuto. Il punto che lui comincia a contare da 0 quindi quando noi selezioniamo il canale 1 lui indica 0, quando selezioniamo 2 lui indica 1 e cos via; per sistema le cose sommiamo 1, quindi il nostro metodo diventa -(IBAction)cambiaCanale:(UISegmentedControl *)sender { if (acceso) { NSLog(@"Il canale selezionato il numero %d",sender.selectedSegmentIndex+1); } } Ora proviamo ad eseguire il nostro programma e guardiamo il funzionamento sia con linterruttore su On che su Off. Bene, abbiamo completato la definizione dei canali dal controller verso linterfaccia cio verso il View del pattern MVC. Ricordando che il controller mette in comunicazione il Model con View passiamo a definire il canale verso il Model. Quindi prima cosa, il nostro controller dovr parlare con un oggetto televisore, ha quindi bisogno di gestire un riferimento ad unistanza di Televisore, quindi in TVController.h aggiungiamo @property (nonatomic,retain) Televisore *ilTelevisore; abbiamo cio dichiarato una property con la quale gestiremo il riferimento al televisore. XCode in questo momento vi segnala un errore, perch? Perch la classe controller non conosce il termine Televisore, dobbiamo quindi farglielo conoscere aggiungendo

#import "Televisore.h" dopo la prima riga di import gi presente. Passiamo a TVController.m e avendo una nuova property aggiungiamo @synthesize ilTelevisore; Poi cambiamo il metodo accendiSpegni per comunicare con il Model, cio lo facciamo diventare -(IBAction)accendiSpegni:(id)sender { NSLog(@"Metodo accendi/spegni"); acceso=!acceso; if (acceso) { schermo.backgroundColor=[UIColor lightGrayColor]; [ilTelevisore accendi]; } else { schermo.backgroundColor=[UIColor blackColor]; [ilTelevisore spegni]; } } cio, nel ramo di accensione (il primo) abbiamo aggiunto [ilTelevisore accendi] cio linvocazione del metodo accendi della classe Televisore, e nel ramo di spegnimento abbiamo aggiunto [ilTelevisore spegni] cio linvocazione del metodo spegni della classe Televisore; in questo modo attraverso i metodi del controller gli eventi sullinterruttore si propagano fino al modello. In maniera analoga cambiamo il metodo regolaVolume in -(IBAction)regolaVolume:(UISlider *)sender { if (acceso) { NSLog(@"Il volume a livello %f", sender.value); livelloVolume.text=[NSString stringWithFormat:@"%f",sender.value]; ilTelevisore.livelloVolume=(NSDecimalNumber *) [NSDecimalNumber numberWithFloat:sender.value];

} } abbiamo cio aggiunto ilTelevisore.livelloVolume=(NSDecimalNumber *) [NSDecimalNumber numberWithFloat:sender.value]; ilTelevisore.livelloVolume laccesso alla property che abbiamo definito nella classe Televisore per loggetto ilTelevisore; quindi con questa istruzione assegnamo un valore alla property livelloVolume del modello. Il valore che assegnamo il risultato di (NSDecimalNumber *)[NSDecimalNumber numberWithFloat:sender.value] Prendiamo prima in considerazione [NSDecimalNumber numberWithFloat:sender.value] questo un metodo di classe che NSDecimalNumber eredita dalla sua superclasse NSNumber e che restituisce un NSNumber che rappresenta il numero frazionario che gli viene passato (sender.value). Il punto che la nostra property livelloVolume di tipo NSDecimalNumber mentre questo metodo restituisce un NSNumber, quindi ricorriamo al type casting inserendo (NSDecimalNumber *) che dice che il risultato va interpretato come istanza di NSDecimalNumber. Il type casting un meccanismo che ci consente di forzare un tipo su un dato, ovviamente resta nostra la responsabilit di controllare che il nuovo tipo e la struttura dati siano compatibili, in quanto il type casting,non trasforma un dato in un altro, non crea nuovi oggetti di un altro tipo ma forza solo la tipizzazione del dato. Infine cambiamo il metodo cambiaCanale in -(IBAction)cambiaCanale:(UISegmentedControl *)sender { if (acceso) { NSLog(@"Il canale selezionato il numero

%d",sender.selectedSegmentIndex+1); ilTelevisore.canaleSintonizzato=(NSDecimalNumber *) [NSDecimalNumber numberWithInt:sender.selectedSegmentIndex]; } } In maniera analoga al caso precedente abbiamo aggiunto la riga per laggiornamento della property canaleSintonizzato con il valore del segmented control dellinterfaccia. Ma in realt noi in questo metodo dobbiamo fare anche unaltra cosa e cio aggiornare la label nella view che fa da schermo visualizzando il nome del canale, dobbiamo cio aggiungere unistruzione come nomeCanale.text=... cio lassegnamento alla property text della label nomeCanale di.gi di cosa? cosa ci restituisce il nome del canale sintonizzato? A questo punto ci accorgiamo che non abbiamo nessun metodo che lo faccia, quindi il nostro modello non allineato con la nostra vistache facciamo? Possiamo decidere o di modificare la View (togliere la label) o di modificare il Model (accedere al nome del canale). Noi scegliamo la seconda strada. Quindi nella nostra classe televisore dovremo trovar eil modo di farci restituire il nome del canale, per far questo usiamo una property, avremmo potuto usare un metodo e sarebbe stato ugualmente corretto. Andiamo in Televisore.h e aggiungiamo @property(readonly) NSString *nomeDelCanale; cio dichiariamo una property in sola lettura nomeDelCanale che sar unistanza di NSString. In Televisore.m, invece di fare il synthesize che creerebbe automaticamente il getter, lo definiamo noi aggiungendo -(NSString *)nomeDelCanale {

return [canaliMemorizzati objectAtIndex:[canaleSintonizzato intValue]]; } cio il metodo getter della property restituisce lelemento dellarray con i nomi dei canali (canaliMemorizzati) che si trova alla posizione indicata da canaleSintonizzato; esattamente quello che cercavamo. A questo punto possiamo tornare al nostro metodo cambiaCanale e modificarlo cos -(IBAction)cambiaCanale:(UISegmentedControl *)sender { if (acceso) { NSLog(@"Il canale selezionato il numero %d",sender.selectedSegmentIndex+1); ilTelevisore.canaleSintonizzato=(NSDecimalNumber *) [NSDecimalNumber numberWithInt:sender.selectedSegmentIndex]; nomeCanale.text=ilTelevisore.nomeDelCanale; } } Abbiamo aggiunto listruzione nomeCanale.text=ilTelevisore.nomeDelCanale; cio alla property text della label nomeCanale assegnamo il valore della property nomeDelCanale delloggetto ilTelevisore. Bene, basta cos per ora per il nostro controller e per le sue interazioni con il Model e con il View; lultima cosa che dobbiamo fare parte da una domanda: ma loggetto della classe Televisore dov? Noi nel controller abbiamo usato una property ilTelevisore, ma non abbiamo mai creato unistanza assegnata a questa property. Abbiamo quindi bisogno di creare il nostro televisore. Ricordate che nella versione precedente avevamo nel main.m tv1=[[Televisore alloc] init];

Dovremo fare qualcosa del genere anche ora. A differenza del programma precedente per, nei programmi x iOS il main.m meglio lasciarlo perdere ed il punto pi simile il metodo - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions che trovate in myTvAppDelegate.m; questo il metodo che viene invocato quando lapp ha completato le procedure di attivazione. In myTvAppDelegate.h aggiungiamo #import "Televisore.h" #import "TVController.h" In myTvAppDelegate.m, nel metodo didFinishLaunchingWithOptions prima della riga [self.window makeKeyAndVisible]; aggiungiamo ((TVController *)self.window.rootViewController).ilTelevisore= [[Televisore alloc] init]; Questa istruzione un po pi articolata, analizziamola self: labbiamo gi incontrata ed il nome con il quale si individua sempre listanza che la cita, quindi nel nostro caso listanza di appDelegate; self.window: laccesso alla property window di appDelegate che in mainWindow.xib collegata alloggetto Window; self.window.rootViewController: laccesso alla property rootViewController di Window che nel file mainWindow.xib abbiamo collegato ad unistanza di viewController della nostra classe (TVController *)self.window.rootViewController: il type casting del rootViewController con il quale forziamo il tipo a TVController; ((TVController *)self.window.rootViewController).ilTelevisore:

laccesso alla property ilTelevisore del rootViewController; quindi in conclusione con listruzione precedente assegnamo alla property ilTelevisore unistanza appena creata, [[Televisore alloc] init], come abbiamo gi visto nel programma precedente. Bene; eseguiamo lapp e guardiamone leffetto sullinterfaccia e sulla console. 16 Opzioni
iOS, Mac, SDK, Tech Room

Prima di proseguire con il nuovo codice facciamo una piccola ottimizzazione a quanto realizzato nel capitolo precedente. Nella prima parte, mentre collegavamo il controller con linterfaccia e non avevamo ancora disposizione il Model, abbiamo avuto bisogno di sapere se il televisore fosse gi acceso oppure no e lo abbiamo fatto definendo una variabile di istanza (acceso) dentro al controller e mettendo nei metodi del controller la condizione if (acceso) { nel momento in cui colleghiamo il controller al Model questa variabile diventa superflua perch c gi la propriet acceso che ilTelevisore eredita da Elettrodomestico che ha la stessa informazione. Quindi andiamo in TVController.m e nel metodo init togliamo listruzione di inizializzazione di acceso acceso=False; negli altri metodi, sostituiamo if (acceso) con

if (ilTelevisore.acceso) cio testiamo la condizione sulla property acceso di ilTelevisore. Nel metodo accendiSpegni, invece usiamo la condizione if ([sender isOn]) ricordiamo che sender istanza di UISwitch ed loggetto che invoca lazione, cio linterruttore dellinterfaccia. La classe UISwitch fornisce il metodo isOn che restituisce Vero se linterruttore in posizione On e Falso se in posizione Off. Poi andiamo in TVController.h e cancelliamo la dichiarazione della variabile acceso. Eseguiamo il programma per verificare che sia tutto a posto e riprendiamo il nostro percorso. Nel capitolo precedente abbiamo creato linterfaccia verso il nostro televisore, ma rispetto alla versione senza interfaccia grafica ci siamo persi un pezzo: il telecomando. Attualmente noi abbiamo dei controlli di interfaccia che agiscono direttamente su un oggetto della classe Televisore sapreste dire perch questa affermazione vera? Per esercizio ripercorrete il percorso dallevento sullinterfaccia fino al Model. In questo capitolo estenderemo la nostra app per gestire anche un telecomando ed esploreremo un altro elemento di Cocoa: la Tab Bar. Ripartiamo dal pattern MVC. Model. Lestensione del Model labbiamo gi scritta nella versione senza interfaccia ma non labbiamo ancora portata in questo progetto: la nostra classe Telecomando con le sue relazioni con la classe Televisore. Con il bottone destro su myTv scegliamo Add Files to

navighiamo il disco fino a trovare la cartella del nostro vecchio progetto e selezioniamo i due file Telecomando.h e Telecomando.m. Vedremo in seguito se abbiamo bisogno di qualche adeguamento. View. Dobbiamo decidere come realizzare linterfaccia Utente per il telecomando. La definizione di uninterfaccia Utente segue molto il gusto personale, vediamo alcune possibilit. La prima quella di mantenere linterfaccia Utente attuale e spostare il flusso degli eventi di interfaccia verso il telecomando invece che direttamente verso il televisore. Cio, ad esempio, quando linterruttore si sposta si invocano i metodi di accendi e spegni del telecomando invece che quelli del televisore. Questo per cosa comporterebbe? Che perdiamo la possibilit di comandare direttamente il televisore, come se avessimo a casa un televisore senza comandi, ma con la sola possibilit di essere pilotato da un telecomando. Una seconda possibilit, quella di inserire nella vista attuale, oltre ai controlli gi presenti verso il televisore, anche altri controlli che leghiamo al telecomando; eventualmente ingrandendo la view e facendola diventare una scrollView. Una terza possibilit, quella che scegliamo, quella di definire una vista dedicata ai controlli per il telecomando.

Poi metteremo insieme le due viste in un TabBar. La TabBar una forma di aggregazione delle viste, come quella utilizzata dallapp iPod, nella quale in basso presente una barra di selettori (Tab) ognuno dei quali fa accedere ad una vista diversa. Avremo quindi una cosa del tipo

Con la tab bar potremo spostarci tra la vista con i controlli del telecomando e la vista con il televisore. Iniziamo quindi a creare la View per il nostro telecomando. Con il bottone destro sulla cartella myTv in XCode, scegliamo New File, nella sezione iOS selezioniamo il gruppo User Interface e scegliamo il template View; nella finestra successiva scegliamo iPhone come device e infine salviamolo con il nome TelecomandoView. Se necessario apriamo, selezionandolo, il file TelecomandoView.xib appena creato. Abbiamo la View contenitore nella quale andremo a disporre i controlli del telecomando.

Nella Library degli oggetti (pannello inferiore del frame di destra) prendiamo un Round Rect Button e posizioniamolo in alto al centro della View. Con un doppio click sul bottone andiamo in edit del testo e scriviamo On/Off. Con questo controllo comanderemo laccensione e lo spegnimento del televisore attraverso il telecomando. Nella Library prendiamo un Segmented Control e posizioniamolo sotto il Button. Nel pannello Attributes delInspector mettiamo 5 in Segments, e poi con un doppio click su ogni segmento scriviamo i numeri da 1 a 5. Risistemiamo la dimensione del controllo se necessario. Con questo controllo comanderemo il passaggio diretto da un canale allaltro. Ma, questa volta, il funzionamento di questo controllo di tipo a pulsante, cio il bottone non resta selezionato una volta premuto. Per ottenere questo comportamento selezioniamo il controllo e nel pannello Attributes dellInspector attiviamo il flag State Momentary; poi togliamo il flag Selected alla voce Behavior con il segmento Segment 0 selezionato. Nella Library, prendiamo una Label e posizioniamolo sotto il Segmented Control, con il doppio click sulla Label editiamo il testo e scriviamoci Programma. Nella Library prendiamo un Segmented Control e trasciniamolo sotto la Label, con il doppio click su ognuno dei due segmenti scriviamo - sul primo e + sul secondo. Con questo controllo passeremo da un canale al successivo o al precedente. Anche il funzionamento di questo controllo di tipo a pulsante, cio il bottone non resta selezionato una volta premuto. Per ottenere questo comportamento selezioniamo il controllo e nel pannello Attributes dellInspector attiviamo il flag State Momentary; poi togliamo il flag Selected alla voce Behavior con il segmento Segment 0 selezionato. Nella Library, prendiamo una Label e posizioniamolo sotto il Segmented Control, con il doppio click sulla Label editiamo il

testo e scriviamoci Volume. Nella Library prendiamo un Segmented Control e trasciniamolo sotto la Label, con il doppio click su ognuno dei due segmenti scriviamo - sul primo e + sul secondo. Con questo controllo alzeremo o abbasseremo il volume di 1 alla volta. Anche il funzionamento di questo controllo di tipo a pulsante, cio il bottone non resta selezionato una volta premuto. Per ottenere questo comportamento selezioniamo il controllo e nel pannello Attributes dellInspector attiviamo il flag State Momentary; poi togliamo il flag Selected alla voce Behavior con il segmento Segment 0 selezionato. Alla fine dovreste avere qualcosa di simile alla figura

Control Dobbiamo implementare un controller per gestire gli eventi su questa interfaccia. Cliccando con il bottone destro sulla cartella myTv in XCode scegliamo il template UIViewController subclass nel gruppo Cocoa Touch. Lasciamo UIViewController come Superclasse e deselezioniamo targeted for iPad e With XIB for user interface dal momento che noi lo XIB lo abbiamo gi creato. Salviamolo con il nome TCController. Analizziamo ora che tipo di connessione dobbiamo stabilire tra il controller e linterfaccia Abbiamo un Button che deve comunicare al controller levento che stato premuto (Action).

Abbiamo un Segmented Control che deve comunicare al controller il numero del canale (Action). Abbiamo un Segmented Control che deve richiedere al controller di passare al programma successivo o a quello precedente (Action). Abbiamo un Segmented Control che deve richiedere al controller di aumentare o diminuire il volume (Action). Bene, quindi dobbiamo definire dei metodi cui collegare gli oggetti dellinterfaccia, mentre non abbiamo bisogno di Outlet, cio di stabilire dei collegamenti dal controller verso linterfaccia. In TCController.h dichiariamo i metodi -(IBAction)accendiSpegni:(UIButton *)sender; -(IBAction)cambiaCanale:(UISegmentedControl *)sender; -(IBAction)incrementaDecrementaProgramma: (UISegmentedControl *)sender; -(IBAction)incrementaDecrementaVolume:(UISegmentedControl *)sender; e dichiariamo la property verso il Model @property(nonatomic,retain) Telecomando *ilTelecomando; dobbiamo far conoscere alla nostra classe controller, la classe Telecomando che stiamo usando con il solito #import "Telecomando.h" messo in testa a Telecomando.h. Andiamo ora definire i nostri metodi in TCController.m. Prima cosa, visto che abbiamo una property, scriviamo @synthesize ilTelecomando; vi ricordo che con questa istruzione diciamo ad XCode di preparare (automaticamente) i metodi di accesso alla property (getter e setter); la usiamo tutte le volte che non abbiamo bisogno di personalizzare i metodi di accesso.

Passiamo ad implementare il metodo -(IBAction)accendiSpegni:(UIButton *)sender { } questo metodo sar invocato ogni volta che il bottone viene premuto; se guardiamo il nostro Model, cio la classe Telecomando vediamo che abbiamo due metodi (accendiIlTelevisore e spegniIlTelevisore) che dovranno essere richiamati da questo a seconda dellazione da fare. Abbiamo quindi bisogno di unistruzione condizionale if (condizione) { <blocco Vero> } else { <blocco Falso> } dobbiamo allora sapere se il televisore acceso o spento per capire se ora lo stiamo spegnendo o accendendo; ma, a differenza del controller TVController che parla con il televisore e quindi ha accesso alle sue property, il nostro TCController parla con il telecomando; tuttavia andando ad esaminare bene la classe Telecomando vediamo che ha una property (myTv) che si riferisce al televisore pilotato, possiamo quindi verificarne lo stato attraverso questa property; il nostro metodo sar quindi -(IBAction)accendiSpegni:(UIButton *)sender { if (ilTelecomando.myTv.acceso) { [ilTelecomando spegniIlTelevisore]; } else { [ilTelecomando accendiIlTelevisore]; }

} osservate che nel test della condizione abbiamo acceduto ad una property della classe Televisore, in maniera analoga avremmo potuto scrivere -(IBAction)accendiSpegni:(UIButton *)sender { if (ilTelecomando.myTv.acceso) { [ilTelecomando.myTv spegni]; } else { [ilTelecomando.myTv accendi]; } } cio avremmo potuto invocare direttamente anche i metodi di accensione e spegnimento che la classe Televisore eredita da Elettrodomestico; cio dal TCController avremmo potuto bypassare linvocazione dei metodi del Telecomando; qual giusta e qual sbagliata? Nessuna delle due ne giusta n sbagliata dal punto di vista del linguaggio; per a livello Model potremmo aver inserito delle istruzioni (di tracciamento, di controllo, ) nei metodi di Telecomando che nel secondo caso non verrebbero eseguite; valutate quindi di volta in volta quale schema utilizzare. Passiamo al metodo successivo -(IBAction)cambiaCanale:(UISegmentedControl *)sender { if (ilTelecomando.myTv.acceso) { [ilTelecomando vaiAlCanale: (NSDecimalNumber *)[NSDecimalNumber numberWithInt:sender.selectedSegmentIndex]]; } } Prima cosa controlliamo che il televisore sia acceso, se lo lunica cosa che facciamo invocare il metodo vaiAlCanale dellistanza ilTelecomando passandogli lindice del selettore del canale selezionato trasformato in NSDecimalNumber; la discussione sul

modo con il quale creiamo il NSDecimalNumber e sul type casting labbiamo gi vista nel cap. precedente a proposito del metodo regolaVolume e vi rimando a quella parte se necessario. Passiamo al metodo con il quale ci spostiamo sui canali di 1 per volta -(IBAction)incrementaDecrementaProgramma: (UISegmentedControl *)sender { } In questo metodo dobbiamo come prima cosa sapere se dobbiamo passare al precedente (bottone -, segmento 0) o al successivo (bottone +, segmento 1). Introduciamo un nuovo statement chiamato switch/case. E una forma di istruzione condizionale a pi condizioni che ha la struttura switch (espressione) { case valore1: break; case valore2: break; case valore3: break; default:

break;

} Il suo comportamento : si valuta lespressione (deve essere unespressione che restituisce un valore intero) e se il risultato valore1 si eseguono le istruzioni che si trovano dopo valore1: fino al primo break incontrato; se il risultato valore2 si eseguono le istruzioni che si trovano dopo valore2: fino al primo break incontrato, e cos via; se il risultato non nessuno dei valori indicati si eseguono le istruzioni dopo default. E necessario porre attenzione alluso dei break; perch se non si mette un break in una condizione, saranno eseguite anche le istruzioni delle condizioni successive fino ad incontrare un break o la }. Con questa istruzione il nostro metodo inizia ad assumere laspetto -(IBAction)incrementaDecrementaProgramma: (UISegmentedControl *)sender { switch (sender.selectedSegmentIndex) { case 0: break; case 1: break; default:

break; } } Abbiamo gi visto che selectedSegmentIndex, una property di UISegmentedControl, che restituisce lindice del selettore selezionato. Sulla base dellindice selezionato, se stato premuto il primo tasto viene eseguito il gruppo case 0, dove gestiremo il passaggio al canale precedente; mentre se stato premuto il secondo tasto viene eseguito il gruppo case 1 dove gestiremo il passaggio al canale successivo. Prima di fare la variazione di canale dobbiamo per verificare se questa possibile cio dobbiamo sapere se siamo gi al numero minimo o massimo di canale e nel caso non fare nulla; altrimenti passeremo al canale corretto. La prima informazione di cui abbiamo bisogno il numero del canale sintonizzato attualmente; questa informazione una property del televisore e quindi la recupereremo attraverso ilTelecomando.myTv.canaleSintonizzato cio attraverso la property myTv di ilTelecomando accediamo al televisore pilotato e da questo possiamo accedere alla property canaleSintonizzato. Aggiungiamo quindi il controllo sui limiti del numero di canale al nostro metodo che diventa -(IBAction)incrementaDecrementaProgramma: (UISegmentedControl *)sender { switch (sender.selectedSegmentIndex) { case 0: if ([ilTelecomando.myTv.canaleSintonizzato intValue] > 0) { } break;

case 1: if ([ilTelecomando.myTv.canaleSintonizzato intValue] < 4) { } break; default: break; } } Nel case 0 abbiamo introdotto il test [ilTelecomando.myTv.canaleSintonizzato intValue] > 0 con ilTelecomando.myTv.canaleSintonizzato, come abbiamo detto sopra, accediamo alla property canaleSintonizzato della classe Televisore; questa property referenzia unistanza di NSDecimalNumber (sottoclasse di NSNumber) pu quindi ricevere linvocazione del metodo intValue (ereditato da NSNumber ) che restituisce il valore intero rappresentato dallistanza stessa. A questo punto controlliamo che il valore sia maggiore (>) di 0, che vuol dire che possiamo ancora diminuire il numero del canale. In maniera analoga, nel case 1, verifichiamo [ilTelecomando.myTv.canaleSintonizzato intValue] < 4 che il valore sia minore (<) di 4, che vuol dire che possiamo ancora aumentare il numero del canale. Iniziamo ora a gestire il case 0, cio dobbiamo passare al canale precedente. Andando a guardare il nostro Model, vediamo che la classe Telecomando non ha metodi di incremento e decremento canali ma ha solo il metodo vaiAlCanale che prende come parametro il numero del canale da sintonizzare. Potremmo decidere di estendere il modello con una coppia di

metodi di incremento e decremento canali oppure di continuare ad usare il solo metodo gi disponibile, e noi adottiamo questa scelta. Quello che dovremo scrivere sar dunque qualcosa del tipo [ilTelecomando vaiAlCanale:]; con al posto dei un NSDecimalNumber che rappresenti un valore inferiore di 1 rispetto a quello attualmente selezionato. Osserviamo che ilTelecomando.myTv.canaleSintonizzato listanza di NSDecimalNumber che rappresenta il valore correntemente sintonizzato, andando sulla documentazione a guardare la classe NSDecimalNUmebr vediamo che disponibile un metodo di istanza (decimalNumberBySubtracting) per sottrarre un NSDecimalNumber da un altro; quello che dobbiamo costruire quindi [ilTelecomando.myTv.canaleSintonizzato decimalNumberBySubtracting:] con al posto dei un NSDecimalNumber che rappresenti il valore 1; osservando ancora la classe NSDecimalNumber notiamo il metodo di classe one, questo metodo (che tra laltro abbiamo gi usato) restituisce proprio loggetto NSDecimalNumber che rappresenta il valore 1; quindi lespressione per ottenere unistanza di NSDecimalNumber che rappresenta il canale precedente sar [ilTelecomando.myTv.canaleSintonizzato decimalNumberBySubtracting:[NSDecimalNumber one]] e questo sar largomento del metodo vaiAlCanale case 0: if ([ilTelecomando.myTv.canaleSintonizzato intValue] > 0) { [ilTelecomando vaiAlCanale: [ilTelecomando.myTv.canaleSintonizzato decimalNumberBySubtracting:[NSDecimalNumber one]]];

} break; In maniera analoga, in case 1, usiamo la stessa istruzione sostituendo per al metodo decimalNumberBySubtracting il metodo decimalNumberByAdding, per sommare 1 al canale corrente. Il metodo diventa quindi -(IBAction)incrementaDecrementaProgramma: (UISegmentedControl *)sender { if (ilTelecomando.myTv.acceso) { switch (sender.selectedSegmentIndex) { case 0: if ([ilTelecomando.myTv.canaleSintonizzato intValue] > 0) { [ilTelecomando vaiAlCanale: [ilTelecomando.myTv.canaleSintonizzato decimalNumberBySubtracting:[NSDecimalNumber one]]]; } break; case 1: if ([ilTelecomando.myTv.canaleSintonizzato intValue] < 4) { [ilTelecomando vaiAlCanale: [ilTelecomando.myTv.canaleSintonizzato decimalNumberByAdding:[NSDecimalNumber one]]]; } break; default: break; } } } Osservate che ho inserito anche qui il controllo se il televisore acceso. Teniamo per ora questa come versione finale, anche se presenta un

problema che per ora non vi segnalo. Passiamo al metodo con il quale aumentiamo o diminuiamo il volume di 1 per volta -(IBAction)incrementaDecrementaVolume:(UISegmentedControl *)sender { } Anche in questo metodo dobbiamo come prima cosa sapere se dobbiamo diminuire (bottone -, segmento 0) o aumentare (bottone +, segmento 1) il volume. Anche qui useremo lo statement switch/case. -(IBAction)incrementaDecrementaVolume:(UISegmentedControl *)sender { switch (sender.selectedSegmentIndex) { case 0: break; case 1: break; default: break; } } In questo caso non abbiamo bisogno di verificare che il livello del volume si mantenga entro i limiti perch questo gi garantito dal Model nei metodi alzaIlVolme e abbassaIlVolume della classe Televisore. Inoltre la classe Telecomando espone gi due metodi alzaIlVolumeDelTelevisore e abbassaIlVolumeDelTelevisore che possiamo invocare in risposta ai due eventi di interfaccia, quindi il nostro metodo diventa -(IBAction)incrementaDecrementaVolume:(UISegmentedControl *)sender {

if (ilTelecomando.myTv.acceso) { switch (sender.selectedSegmentIndex) { case 0: [ilTelecomando abbassaIlVolumeDelTelevisore]; break; case 1: [ilTelecomando alzaIlVolumeDelTelevisore]; break; default: break; } } } con il solito inserimento del controllo sul televisore acceso. Abbiamo cos finito di definire il controller (per ora). Andiamo ora a stabilire i collegamenti con linterfaccia. Apriamo il file TelecomandoView.xib e selezioniamo Files owner nel gruppo placeholders. Vi ricordo che questo il segnaposto che a si riferir al nostro controller, quindi la prima cosa da fare dire di che classe sar il nostro controller. Nel pannello Identity dellInspector, nel campo Class scegliamo la classe TCController. Il primo collegamento da fare quello per dire quale View gestita da questo controller, clicchiamo con il bottone destro su Files owner e nella finestra che si apre colleghiamo lOutlet view con la view generale dellinterfaccia, cio quella che contiene tutti gli altri controlli. A questo punto trasciniamo, con il bottone destro premuto, il mouse dal Button a Files owner e scegliamo accendiSpegni (dovrebbe esservi proposto solo questo); facciamo lo stesso dal selettore dei canali e dagli altri due controlli e scegliamo di volta in volta il metodo che vogliamo che sia eseguito per gestire levento corrispondente.

Abbiamo cos completato la prima parte dellestensione, cio il pattern MVC per gestire il telecomando. Adesso dobbiamo mettere insieme la gestione del televisore con la gestione del telecomando in un TabBar. Andiamo in MainWindow.xib; in questo momento il rootViewController quello del televisore e a noi non va bene. Clicchiamo con il destro sulloggetto Window e nel popUp che appare clicchiamo sulla x della property rootViewController in modo da sganciare il controller attualmente presente. A questo punto cancelliamo, nellarea Objects, loggetto viewController che avevamo inserito precedentemente e che era usato come rootViewController. Nella Library scegliamo Tab Bar Controller e trasciniamolo nellarea Objects sotto Window. A questo punto con il bottone destro su Window riapriamo il popUp e colleghiamo rootViewController al Tab Bar Controller. Selezioniamo loggetto Tab Bar Controller e sar visualizzata la View con la Tab Bar in basso, con due elementi. Facciamo doppio click sul primo per andare in edit sulla label dellitem e scriviamoci Telecomando, poi facciamo doppio click sul secondo e scriviamoci Televisore. Sono solo nomi che diamo alle sezioni e non hanno nessuna relazione con le classi che abbiamo creato, quindi potete anche chiamarli in altro modo. Ora selezionate il primo dei due View Controller contenuti nel Tab Bar Controller (se necessario esplodetelo con il triangolino vicino alloggetto) e nellIdentity Inspector alla voce Class selezionate TCController. NellAttributes Inspector alla voce NIB Name scrivete TelecomandoView. Ora selezionate il secondo View Controller e fate la stessa cosa ovviamente dando TVController come Class e TelevisoreView come NIB Name. Se ora provate ad eseguire lapplicazione otterrete un errore alla

riga ((TVController *)self.window.rootViewController).ilTelevisore=[[Televisore alloc] init]; del metodo application:didFinishLaunchingWithOptions:, perch? Perch ora il nostro rootViewController non pi unistanza di TVController ma di UITabBarController e quindi non conosce la property ilTelevisore. Per ora cancelliamo questa riga per provare linterfaccia poi vedremo cosa dobbiamo ancora fare. Quindi cancelliamo la riga ed eseguiamo lapp, vediamo che riusciamo a spostarci da una view allaltra e che possiamo cliccare sui controlli; verifichiamo che i controlli non siano coperti dalla Tab Bar, nel caso andiamo nel file XIB corrispondente e risistemiamoli. Se dal punto di vista dellinterfaccia abbiamo fatto dei passi avanti, dal punto di vista funzionale le cose sembrano tornate indietro, nel senso che i comandi del televisore non sembrano avere pi leffetto corretto. Questo perch in questo momento abbiamo gli oggetti del livello View e gli oggetti del livello Control, ma non abbiamo gli oggetti del livello Model. Abbiamo le classi (Televisore, Telecomando,) ma non abbiamo creato alcun oggetto di queste classi. In effetti la riga che abbiamo cancellato poco fa faceva esattamente questo, con [[Televisore alloc] init] noi creavamo unistanza di Televisore e la assegnavamo alla property ilTelevisore di un TVController. Dobbiamo fare una cosa analoga. In myTvAppDelegate.h aggiungiamo #import "Telecomando.h" #import "TCController.h" in modo che siano conosciute ed utilizzabili. Nel metodo application:didFinishLaunchingWithOptions: subito

dopo la { scriviamo Televisore *tv1=[[Televisore alloc] init]; Telecomando *tc1=[[Telecomando alloc ] init]; tc1.myTv=tv1; In questo modo abbiamo creato due oggetti tv1 e tc1 istanze rispettivamente di Televisore e Telecomando. Inoltre abbiamo agganciato il televisore tv1 al telecomando assegnandolo alla sua property myTv. Lultima cosa che ci resta da fare assegnare questi due oggetti alle relative property dei due controller; il punto proprio questocome recuperiamo i due controller, visto che li abbiamo creati nello XIB e non nel codice? Il meccanismo sempre lo stesso: abbiamo bisogno di far parlare il codice che scriviamo con oggetti definiti in uno XIB quindi usiamo gli Outlet, cos come facciamo per gli elementi grafici. Andiamo in myTvAppDelegate.h ed aggiungiamo @property (nonatomic, retain) IBOutlet TCController *myTCController; @property (nonatomic, retain) IBOutlet TVController *myTVController; Come tutte le property per le quali usiamo i metodi di default, in myTvAppDelegate.m, dopo la riga @implementation myTvAppDelegate Scriviamo @synthesize myTCController; @synthesize myTVController; vi ricordo che in questo modo XCode prepara automaticamente i metodi di accesso (getter/setter) alle property. A questo punto, torniamo nel metodo application:didFinishLaunchingWithOptions: e, dopo la creazione di tc1 e tv1 scriviamo:

myTCController.ilTelecomando=tc1; myTVController.ilTelevisore=tv1; cio assegnamo alle due property dei controller gli oggetti appena creati. Lultima cosa da fare collegare i nostri due Outlet ai controller nello XIB. Apriamo il file MainWindow.xib, clicchiamo con il bottone destro sulloggetto My Tv App Delegate e dal popUp che appare collegate la property myTCController al primo dei due controller dentro Tab Bar Controller e collegate myTVController al secondo. Eseguiamo lapplicazione ed osserviamo il comportamento sia con i tasti del televisore sia con quelli del telecomando. Se guardiamo i messaggi a console vediamo che tutto funziona correttamente mentre leffetto sulle interfacce non corretto. I comandi dati tramite linterfaccia del telecomando non influiscono sullinterfaccia del televisore (es. se accendete dal telecomando lo schermo resta nero e lo switch del televisore resta su Off) rendendo in breve inconsistenti le due interfacce; in effetti perch dovrebbe? Noi tramite linterfaccia del telecomando parliamo solo con il Modello non c nessun motivo per cui si dovrebbe aggiornare linterfaccia del televisore. Una possibilit per risolvere questo problema quella di individuare il momento in cui si passa alla visualizzazione del televisore ed aggiornare in quel momento lo stato dellinterfaccia in maniera coerente con lo stato del Modello. In TVController.m aggiungiamo - (void)viewWillAppear:(BOOL)animated { NSLog(@"Eccomi...."); } non c bisogno di dichararlo nel .h perch un metodo predefinito per UIViewController, di cui TVController sottoclasse. Ora eseguiamo lapp e passiamo pi volte dal tab

telecomando al tab televisore ed osserviamo la console. Possiamo vedere che ogni volta che si passa allinterfaccia del televisore viene eseguito il metodo (e in effetti proprio il suo scopo) possiamo quindi usarlo per laggiornamento dellinterfaccia. Iniziamo ad aggiornare lo stato del televisore - (void)viewWillAppear:(BOOL)animated { if (ilTelevisore.acceso) { schermo.backgroundColor=[UIColor lightGrayColor]; } else { schermo.backgroundColor=[UIColor blackColor]; } } cio controlliamo se il televisore acceso, nel qual caso accendiamo lo schermo (grigio) altrimenti lo spegnamo (nero). Eseguiamo e proviamo. Per non basta, dobbiamo anche aggiornare lo stato dellinterruttore, altrimenti potremmo trovarci con lo schermo acceso e linterruttore su Off. Abbiamo quindi bisogno che il controller comunichi lo stato allinterfaccia, quindi abbiamo bisogno di un Outlet verso linterruttore. In TVController.h aggiungiamo @property (nonatomic, retain) IBOutlet UISwitch *interruttore; e come al solito per le property, in TVController.m @synthesize interruttore; Andiamo ora a fare il collegamento dellOutlet con linterfaccia. Andiamo in TelevisoreView.xib e con il bottone destro su Files owner apriamo il popup; colleghiamo lOutlet interruttore con loggetto grafico Switch. Torniamo a viewWillAppear e modifichiamolo in - (void)viewWillAppear:(BOOL)animated { if (ilTelevisore.acceso) {

schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } } cio nel ramo del televisore acceso abbiamo assegnato il valore TRUE (Vero) alla property on di interruttore; mentre nel ramo spento assegnamo il valore FALSE (Falso). Lo stato di questa property predefinita per UISwitch ne comanda la visualizzazione. Ora abbiamo bisogno di accendere il corretto selettore del canale sintonizzato, anche qui abbiamo bisogno di un Outlet in quanto il controller deve comunicare con linterfaccia. Aggiungiamo @property (nonatomic, retain) IBOutlet UISegmentedControl *numCanale; e la relativa @synthesize nel .m; e facciamo il collegamento nel file XIB. Modifichiamo viewWillAppear - (void)viewWillAppear:(BOOL)animated { if (ilTelevisore.acceso) { schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } numCanale.selectedSegmentIndex=[ilTelevisore.canaleSintonizzat o intValue]; nomeCanale.text=ilTelevisore.nomeDelCanale; } quello che facciamo assegnare un valore alla property selectedSegmentIndex del segmented control con il numero del selettore da selezionare. Il valore che usiamo per attivare il

selettore quello rappresentato dalla property canaleSintonizzato di ilTelevisore. Inoltre aggiorniamo la label con il nome del canale. Infine bisogna aggiornare linterfaccia anche relativamente al livello del volume. Anche qui abbiamo bisogno di un Outlet verso lo slider del volume. @property (nonatomic, retain) IBOutlet UISlider *volume; Definiamo anche la synthesize ed il collegamento nello XIB. Modifichiamo il metodo viewWillAppear - (void)viewWillAppear:(BOOL)animated { if (ilTelevisore.acceso) { schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } numCanale.selectedSegmentIndex=[ilTelevisore.canaleSintonizzat o intValue]; nomeCanale.text=ilTelevisore.nomeDelCanale; volume.value=[ilTelevisore.livelloVolume floatValue]; livelloVolume.text = [NSString stringWithFormat:@"%f",volume.value]; } Abbiamo assegnato un valore alla property value dello slider che ne identifica lo stato e la posizione e gli assegnamo il valore della property di ilTelevisore interpretato come numero razionale (floatValue) che il tipo della property value; inoltre abbiamo anche aggiornato la label con il livello del volume. Bene. Dovremmo aver finito, eseguiamo lapplicazione e vediamone gli effetti usando entrambe le interfacce. Vi invito a ripercorrere con attenzione gli ultimi capitoli e

assimilare bene i concetti espressi anche facendo delle prove. Vi allego il progetto nella versione finale di questo capitolo.

17 Il KVO
iOS, Mac, OSX, SDK, Tech Room

In questo capitolo faremo di nuovo un po di ottimizzazione dei concetti visti con lintroduzione di un nuovo pattern. Rivediamo cosa abbiamo fatto nel capitolo precedente: abbiamo introdotto un altro oggetto, un telecomando, che comunica, a livello Model, con un oggetto televisore e a livello Control con un controller. Il televisore a sua volta comunica con un controller e i due controller comunicano ognuno con una propria vista. In pratica abbiamo realizzato una cosa del tipo.

Se osserviamo la parte televisore vediamo che il controller parla con le istanze del Model solo con il meccanismo delle invocazioni dirette di metodi, in pratica accedendo alle property del modello. Quale problema ci siamo trovati ad affrontare, proprio a causa di questo tipo di relazione? Il fatto che le variazioni che arrivano alle istanze di Televisore attraverso linterfaccia del telecomando devono avere effetto sullinterfaccia del televisore.

Avendo a disposizione lunico meccanismo dellinterrogazione di property lo abbiamo risolto allineando linterfaccia con il modello nel metodo viewWillAppear accedendo alle property del televisore controllato; ma questo modo, come potete immaginare pu esser utilizzato in situazioni come questa dove abbiamo una sola classe come modello e una sola vista, non appena la realt si complica un po farebbe diventare in breve tempo il viewWillAppear troppo pesante. Come si pu generalizzare questa situazione? Con il meccanismo delle Actions noi implementiamo un ragionamento del tipo quando succede qualcosa sullinterfaccia fai

ma quando diversi eventi provenienti da diverse viste possono avere effetto sugli stessi elementi di interfaccia, questo meccanismo diventa inefficiente e sarebbe pi utile implementare un ragionamento del tipo quando cambia un valore del modello fai cio il controller si attiva alla variazione di valore di una property indipendentemente da chi ha causato quella variazione. E importante comprendere la differenza tra questi due approcci. Per implementare questo tipo di interazione facciamo ricorso ad un altro pattern (cio un modello di interazione tra oggetti) il KeyValue Observing (KVO). Tramite questo pattern un oggetto viene informato dal sistema quando una certa propriet di un altro oggetto cambia valore. Che esattamente quello che vogliamo. Questo pattern si pu utilizzare ogni volta che si ritiene necessario ma sicuramente trova nellinterazione tra Model e Control una delle sue applicazioni principali. Approfondimento: Cercate nella documentazione Apple KeyValue Observing Programming Guide e leggete i primi 2 capitoli Andiamo ad applicare il KVO in modo da capirne il funzionamento. Iniziamo a gestire levento accensione del televisore. Dal punto di vista del KVO questo significa che il controller istanza di TVController dovr essere informato dal sistema quando cambia il valore della property acceso dellistanza di Televisore che stiamo vedendo. Andiamo nel file myTvAppDelegate.m e, modifichiamo il metodo - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { // Override point for customization after application launch.

Televisore *tv1=[[Televisore alloc] init]; Telecomando *tc1=[[Telecomando alloc ] init]; tc1.myTv=tv1; myTCController.ilTelecomando=tc1; myTVController.ilTelevisore=tv1; [myTVController.ilTelevisore addObserver:myTVController forKeyPath:@"acceso" options:NSKeyValueObservingOptionNew context:nil]; [self.window makeKeyAndVisible]; return YES; } abbiamo cio aggiunto listruzione [myTVController.ilTelevisore addObserver:myTVController forKeyPath:@"acceso" options:NSKeyValueObservingOptionNew context:nil]; linvocazione del metodo di istanza addObserver:forKeyPath:options:context:, questo il metodo che serve per dire al sistema che un oggetto interessato alle variazioni del valore di una certa property di un altro oggetto. In particolare noi stiamo dicendo a myTVController.ilTelevisore, listanza della classe Televisore che riceve il messaggio con linvocazione del metodo, che loggetto myTvController, parametro di addObserver, interessato alle variazioni della property acceso, parametro di forKeyPath. Con il parametro di options, NSKeyValueObservingOptionNew, specifichiamo che al momento della segnalazione della variazione ci interessa ricevere il nuovo valore della property. Infine in context, possiamo specificare un oggetto che ci verr passato anche esso al momento della segnalazione e che pu essere utile per gestire levento; nel nostro caso abbiamo indicato nil, che vuol dire nessun oggetto. Approfondimento: Cercate nella documentazione Apple il metodo

addObserver:forKeyPath:options:context e analizzate i possibili valori per il parametro options. Bene, a questo punto abbiamo detto che il controller deve essere informato delle variazioni della property; ma come far ad essere informato? La risposta a questa domanda nellimplementazione del metodo -(void)observeValueForKeyPath:(NSString *)keyPath ofObject: (id)object change:(NSDictionary *)change context:(void *)context { } da parte delloggetto osservatore. Questo metodo sar invocato automaticamente dal sistema in risposta alla variazione del valore della property per la quale ci siamo registrati. Analizziamo i parametri di questo metodo, che saranno valorizzati automaticamente alla sua invocazione: keyPath: contiene il nome della property il cui cambio di valore ha generato linvocazione; un parametro importante soprattutto se volessimo osservare diverse propriet; object: contiene loggetto la cui propriet ha cambiato il valore; importante soprattutto se un osservatore interessato a diversi oggetti; immaginate di avere pi televisori; change: contiene alcune informazioni relative al cambiamento avvenuto; context: contiene altre eventuali informazioni aggiuntive relative allevento, corrisponde alloggetto specificato in context nel addObserver:forKeyPath:options:context: che abbiamo usato per registrare il controller come osservatore; Andiamo quindi ad implementare questo metodo scrivendo

-(void)observeValueForKeyPath:(NSString *)keyPath ofObject: (id)object change:(NSDictionary *)change context:(void *)context { } nel file TVController.m. Scriviamoci dentro il codice per gestire levento acceso/spento. Come al solito dobbiamo capire se stiamo accendendo il televisore (acceso vale True) o lo stiamo spegnendo (acceso vale False), come facciamo? Il parametro change ci viene in aiuto proprio in questa occasione. Unistanza di NSDictionary una struttura che pu essere vista come un insieme di coppie chiave/valore ((chiave1 valore1), (chiave2 valore2), (chiave3 valore3),..) che pu essere interrogata attraverso una chiave per ottenerne il valore corrispondente. Approfondimento: Cercate nella documentazione Apple il metodo observeValueForKeyPath:ofObject:change:context: e seguite il link Keys used by the change dictionary. per trovare le chiavi usate nel dictionary associato change. Quindi possiamo scrivere il nostro metodo -(void)observeValueForKeyPath:(NSString *)keyPath ofObject: (id)object change:(NSDictionary *)change context:(void *)context { if ([[change valueForKey:@"new"] boolValue]) { schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } } Abbiamo introdotto unistruzione condizionale che distingue i due casi se stiamo accendendo il televisore o se lo stiamo spegnendo.

Quale condizione andiamo a controllare? [change valueForKey:@"new"] questa linvocazione del metodo valueForKey su change che unistanza di NSDictonary. Il metodo valueForKey un metodo standard per NSDictionary e restituisce il valore associato alla chiave passata come argomento nellistanza di dizionario ricevente. La chiave che stiamo usando per recuperare un valore new, questa chiave restituisce il nuovo valore della property che ha generato linvocazione del metodo. Per usarlo allinterno del test dellistruzione if ci facciamo restituire il dato come valore booleano (Vero/Falso) tramite linvocazione di boolValue. Quindi se la condizione dellif restituisce True vuol dire che il nuovo valore di acceso Vero e quindi dovremo gestire laccensione del televisore, altrimenti dovremo gestire lo spegnimento. Le istruzioni nei blocchi del Vero e del Falso non le commento in quanto sono le stesse che avevamo inserito nel viewWillAppear nel capitolo precedente. Se abbiamo inserito laggiornamento dellinterfaccia in questo metodo dobbiamo toglierlo da tutte le altre parti nelle quali era presente. In particolare in viewWillAppear cancelliamo if (ilTelevisore.acceso) { schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } nel metodo accendiSpegni togliamo le due righe schermo.backgroundColor=[UIColor lightGrayColor]; ... schermo.backgroundColor=[UIColor blackColor]; Proviamo ad eseguire il programma ed a passare pi volte dal tab

telecomando al tab televisore e viceversa accendendo e spegnendo il televisore. Potete osservare che in genere le cose funzionano ma se fate questa sequenza: eseguite lapplicazione dallinterfaccia del telecomando (senza prima essere andati a quella del televisore) cliccate sul pulsante On/Off andate sulla view del televisore vedete che non c stato leffetto voluto (schermo grigio ed interruttore su On). Perch? Per rispondere a questa domanda analizziamo un po meglio la natura del lavoro che facciamo nei file xib. Quando lavoriamo su un file xib (utilizzando in tal modo la componente Interface Builder di XCode) noi disegniamo la nostra interfaccia utente utilizzando delle classi di Cocoa (UIView, UISwitch, UISlider, UILabel,); ogni elemento di interfaccia che aggiungiamo diventa unistanza della propria classe, cio un oggetto, ed a questi oggetti che tramite il collegamento IBOutlet noi inviamo i nostri messaggi (cambia il colore, cambia lo stato, cambia il testo,) per modificare linterfaccia. iOS per ottimizzare luso della memoria per crea effettivamente questi oggetti solo il momento in cui ne ha bisogno, cio solo quando li dovr visualizzare. Nel nostro caso solo quando passeremo al tab televisore, in quanto finch restiamo nel tab telecomando non ha bisogno di mostrare la view dello schermo n nessun altro dei controlli del televisore. Questa ottimizzazione per si scontra con luso del KVO, in quanto i messaggi che invieremo agli IBOutlet legati agli elementi di interfaccia del televisore non arriveranno ad alcun oggetto fino a quando non saremo passati almeno una volta dal tab televisore. La soluzione al problema quindi quella di forzare la creazione degli oggetti di interfaccia prima di cominciare a mandare i messaggi. Sono chiari il problema e la soluzione che andremo ad

implementare? Andiamo nel metodo application:didFinishLaunchingWithOptions: di myTvAppDelegate, e prima di [myTVController.ilTelevisore addObserver:myTVController forKeyPath:@"acceso" options:NSKeyValueObservingOptionNew context:nil]; scriviamo [[NSBundle mainBundle] loadNibNamed:@"TelevisoreView" owner:myTVController options:nil]; NSBundle una classe che fornisce diverse funzionalit per operare sulla struttura dellapplicazione (vi rimando alla documentazione per il suo approfondimento), in particolare mette a disposizione il metodo loadNibNamed che serve proprio a creare gli oggetti definiti in un particolare file xib (senza visualizzarli); il nome del file gli viene passato come argomento insieme alloggetto che andr a sostituire il segnaposto Files owner che abbiamo gi incontrato ogni volta che abbiamo creato uninterfaccia tramite IB. A questo punto eseguiamo lapplicazione e dovremmo avere il comportamento corretto. Bene, ora che il canale KVO funzionante possiamo farci passare le altre segnalazioni (c un ultimo aspetto da affrontare che vediamo immediatamente). Iniziamo dal cambio del canale. Dobbiamo metterci in osservazione della property canaleSintonizzato. Nel metodo didFinishLaunchingWithOptions del file myTvAppDelegate.m aggiungiamo [myTVController.ilTelevisore addObserver:myTVController

forKeyPath:@"canaleSintonizzato" options:NSKeyValueObservingOptionNew context:nil]; la stessa istruzione della precedente registrazione di osservazione e quindi non la discutiamo, osserviamo solo che abbiamo inserito la nuova property che ci interessa osservare, canaleSintonizzato. Passiamo alla gestione della segnalazione nel metodo observeValueForKeyPath:ofObject:change: definito in TVController.m, che, come abbiamo detto e visto, viene invocato automaticamente a fronte delle variazione delle property osservate. Ora per il metodo pu essere invocato in risposta alle variazioni di due property; necessario quindi individuare quale property ha cambiato valore per poter eseguire il corretto blocco di aggiornamento. Ricordiamo che tra gli argomenti che sono passati al metodo c keyPath che unistanza di NSString che contiene esattamente il nome della property che ha generato linvocazione del metodo. Possiamo quindi scrivere -(void)observeValueForKeyPath:(NSString *)keyPath ofObject: (id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"acceso"]) { if ([[change valueForKey:@"new"] boolValue]) { schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } } else { numCanale.selectedSegmentIndex=[ilTelevisore.canaleSintonizzat o intValue]; nomeCanale.text=ilTelevisore.nomeDelCanale; }

} Cio la prima cosa che facciamo allinterno del metodo unistruzione condizionale la cui condizione [keyPath isEqualToString:@"acceso"] keyPath unistanza di NSString, isEqualToString un metodo predefinito per la classe NSString che confronta due oggetti di tipo NSString e restituisce True (Vero) se le stringhe di caratteri contenute sono uguali, altrimento restituisce False (Falso). Attenzione, per confrontare due NSString, non si pu usare loperatore di uguaglianza che abbiamo a volte incontrato in passato, ==, in quanto due istanze di NSString sono oggetti e non sequenze di caratteri. La differenza tra i due tipi di confronto come se io avessi due foglietti di carta con scritto sopra la parola acceso. Se confrontiamo i foglietti, questi saranno sicuramente diversi, potrebbero essere uno a righe e laltro a quadretti, uno rettangolare e laltro triangolare, uno pi grande e laltro pi piccolo; ma se confronto quello che c scritto vedr che linformazione la stessa: acceso. Il confronto ==, analogo a confrontare i foglietti, il confronto isEqualToString analogo a confrontare linformazione contenuta. Quindi con il test di quella condizione verifichiamo se il metodo stato attivato dalla variazione della property acceso. Nel blocco corrispondente alla condizione Vero abbiamo le istruzioni per la gestione dellaccensione/spegnimento che avevamo gi; mentre nel ramo corrispondente alla condizione Falso, indicativo quindi del fatto che stiamo gestendo la property canaleSintonizzato (visto che non la property acceso) abbiamo le due istruzioni di aggiornamento dellinterfaccia relativa al canale che avevamo inserito nel viewWillAppear e dal quale le togliamo. Inoltre dal metodo cambiaCanale ti TVController.m togliamo listruzione nomeCanale.text=ilTelevisore.nomeDelCanale;

Passiamo ora a gestire il cambiamento del volume. Registriamo il controller per ricever le variazioni della property livelloVolume, aggiungendo [myTVController.ilTelevisore addObserver:myTVController forKeyPath:@"livelloVolume" options:NSKeyValueObservingOptionNew context:nil]; nel metodo didFinishLaunchingWithOptions del file myTvAppDelegate.m. Andiamo a gestire la segnalazione nel metodo observeValueForKeyPath:ofObject:change: in TVController.m. Ora quando siamo nel ramo else dellistruzione if pi esterna (quella con la condizione sul nome della property) sappiamo solo che non stiamo gestendo la property acceso; ma non sappiamo se cambiato canaleSintonizzato o livelloVolume. Dobbiamo quindi aggiungere un ulteriore test sul nome della property. -(void)observeValueForKeyPath:(NSString *)keyPath ofObject: (id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"acceso"]) { if ([[change valueForKey:@"new"] boolValue]) { schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } } else { if ([keyPath isEqualToString:@"canaleSintonizzato"]) { numCanale.selectedSegmentIndex=[ilTelevisore.canaleSintonizzat o intValue]; nomeCanale.text=ilTelevisore.nomeDelCanale; } else {

volume.value=[ilTelevisore.livelloVolume floatValue]; livelloVolume.text = [NSString stringWithFormat:@"%f",volume.value]; } } } vi lascio lanalisi dellistruzione if che abbiamo inserito, vi faccio solo osservare che abbiamo spostato qui le due istruzioni di aggiornamento del volume che avevamo in viewWillAppear che a questo punto vuoto e si pu cancellare. Eliminate infine dal metodo regolaVolume di TVController.m livelloVolume.text=[NSString stringWithFormat:@"%f",sender.value]; Eseguite ancora una volta lapplicazione e verificate il comportamento corretto. Ripercorrete questo capitolo per consolidare il pattern KVO e gli aspetti connessi. 18 Un po di contenuti
iOS, Mac, OSX, SDK, Tech Room

Proviamo ad estendere il nostro programma per dare un po di colore alla nostra tv che per ora scrive solo il nome del canale nello schermo. La prima cosa da fare prima di mettersi a scrivere il codice definire cosa si vuole ottenere. Decidiamo che il nostro televisore comincia a farci vedere qualcosa: nei canali da 1 a 3 saranno mostrate delle immagini, nel canale 4 sar visualizzato un video e nel canale 5 un sito. Iniziamo con il procurarci tre immagini formato png; prendetele dal vostro disco o scaricatele da Internetin ogni caso prendete le

tre immagini e trascinatele dentro la cartella myTv di XCode. Accertatevi che sia attivo il flag copy items into destination e andate avanti. Rinominate le tre immagini Onda1.png, Onda2.png e Onda3.png. Ora per vedere le immagini in unapp necessario ricorrere ad un oggetto (poteva essere diversamente?); in particolare necessaria unistanza d UIImageView. Selezioniamo il file TelevisoreView.xib in modo da aprire la componente Interface Builder di XCode, cio lo strumento con il quale disegniamo la nostra interfaccia. Come prima cosa spostiamo la label con il nome del canale nellangolo in basso a sinistra in modo da non avere questa scritta al centro dei nostri contenuti, se trovate difficoltoso farlo perch lo schermo nero vi impedisce di vedere la label, tramite lAttributes Inspector cambiate il colore della view e poi rimettetelo a Black. Nella Library degli oggetti disponibili, la parte inferiore del frame di destra, scegliamo Image View e trasciniamola dentro la View schermo, nel pannello Objects a sinistra potete vedere la Image View allinterno della gerarchia della vista schermo. Spostiamo e ridimensioniamo la Image View in modo che si sovrapponga esattamente alla view schermo; a questo punto dal pannello Objects (a sinistra) la trasciniamo fuori dalla gerarchia schermo e la mettiamo allo stesso livello della view esterna contenitore, alla fine avrete qualcosa di questo tipo

Ora abbiamo quindi bisogno di un comportamento diverso nellinterfaccia a seconda del canale selezionato. Dove possiamo programmare questo comportamento? Direi nel metodo che usiamo per ridisegnare linterfaccia a seconda dellevento, cio quello dove aggiorniamo la label con il nome del canale observeValueForKeyPath:ofObject:change:. Se guardate la documentazione di UIImageView vedete che ha una property image di tipo UIImage. Questa property si riferisce alloggetto, istanza di UIImage, che contiene limmagine che deve essere mostrata. Quindi il nostro codice dovr assegnare il corretto valore a questa property, vuol dire che abbiamo bisogno di un legame dal codice allinterfaccia, cio un IBOutlet verso la UIImageView. Aggiungiamo in TVController.h @property (nonatomic, retain) IBOutlet UIImageView *immagineCanale; ed in TVController.m la corrispondente

@synthesize immagineCanale; Colleghiamo nella componente Interface Builder il nostro IBOutlet alloggetto di interfaccia. Selezioniamo il file TelevisoreView.xib, e cliccando con il destro su Files owner colleghiamo loutlet immagineCanale alla UIImageView trascinando il mouse con il sinistro premuto. Vediamo ora come scrivere il codice del metodo e poi lo analizziamo. -(void)observeValueForKeyPath:(NSString *)keyPath ofObject: (id)object change:(NSDictionary *)change context:(void *)context { ... if ([keyPath isEqualToString:@"canaleSintonizzato"]) { numCanale.selectedSegmentIndex=[ilTelevisore.canaleSintonizzat o intValue]; nomeCanale.text=ilTelevisore.nomeDelCanale; switch ([ilTelevisore.canaleSintonizzato intValue]) { case 0: case 1: case 2: immagineCanale.image=[UIImage imageNamed:ilTelevisore.nomeDelCanale]; [schermo addSubview:immagineCanale]; break; default: break; } } else { volume.value=[ilTelevisore.livelloVolume floatValue]; livelloVolume.text = [NSString stringWithFormat:@"%f",volume.value]; } } }

Operiamo sul ramo if nel quale abbiamo verificato che levento che stiamo gestendo il cambio di canale (cio stato assegnato un valore alla property canaleSintonizzato de ilTelevisore, ricordate il pattern KVO visto nel cap. precedente). Allinterno di questo ramo, dopo le prime due istruzioni che cerano gi e quindi non discutiamo, abbiamo aggiunto unistruzione switch; labbiamo incontrata gi qualche capitolo fa, il suo comportamento quello di valutare lespressione tra parentesi tonda, che deve restituire un valore intero, ed in base al risultato di questa valutazione esegue le istruzioni partendo dal gruppo case relativo al valore risultante fino al primo break incontrato. Il nostro switch switch ([ilTelevisore.canaleSintonizzato intValue]) { case 0: case 1: case 2: immagineCanale.image=[UIImage imageNamed:ilTelevisore.nomeDelCanale]; [schermo addSubview:immagineCanale]; break; default: break; } Lespressione da valutare [ilTelevisore.canaleSintonizzato intValue] che restituisce il valore intero (intValue) delloggetto riferito dalla property canaleSintonizzato (che un NSDecimalNumber) de ilTelevisore, cio ci restituir 0,1,2,3,4 a seconda del canale sintonizzato. Per come abbiamo deciso di visualizzare i contenuti, i primi 3 canali (0,1 e 2) mostrano le 3 foto, possiamo far s che case 0, case 1 e case 2 eseguano la stessa sequenza di istruzioni scrivendo case 0: case 1:

case 2: immagineCanale.image=[UIImage imageNamed:ilTelevisore.nomeDelCanale]; [schermo addSubview:immagineCanale]; break; questo vuol dire che se abbiamo 0, poich cominciamo a scorrere il codice da case 0 fino al primo break eseguiremo le istruzioni scritte dopo case 2:, lo stesso se otteniamo 1 e 2. Quindi in tutti e tre i casi noi eseguiamo immagineCanale.image=[UIImage imageNamed:ilTelevisore.nomeDelCanale]; [schermo addSubview:immagineCanale]; immagineCanale il nostro IBOutlet che referenzia loggetto UIImageView che abbiamo creato in Interface Builder. Come abbiamo detto sopra questa classe espone una property, image, alla quale deve essere assegnata limmagine da visualizzare nella view. Noi stiamo assegnando il risultato di [UIImage imageNamed:ilTelevisore.nomeDelCanale] questa linvocazione di un metodo di classe, imageNamed, della classe UIImage. Questo metodo prendo in input il nome di un file immagine presente nel progetto e restituisce unistanza della classe UIImage che lo rappresenta. Il parametro che noi passiamo in ingresso ilTelevisore.nomeDelCanale che la property che abbiamo dichiarato nella classe Televisore e che restituisce il nome del canale Onda1, Onda2,, in questo modo andremo a selezionare il nome corretto del file da mostrare. Approfondimento: Studiare sulla documentazione Apple la classe UIImage. La seconda istruzione [schermo addSubview:immagineCanale]; linvocazione del metodo addSubview sulla view schermo passando come argomento la nostra UIImageView immagineCanale. La nostra immagineCanale viene aggiunta nella

gerarchia di viste come sotto vista dello schermo; analogo a quello che si fa da Interface Builder spostando loggetto UIImageView dentro la gerarchia della view schermo. Poich la view schermo visualizzata nellinterfaccia questa istruzione ha leffetto di mostrare anche la UIImageView immagineCanale e quindi limmagine contenuta. Provate ad eseguire il programma e verificate che al cambio di canale siano mostrate le vostre immagini. Bene, se tutto funziona, abbiamo messo in piedi il meccanismo centrale del cambio di immagine ma dobbiamo sistemare ancora alcuni particolari prima di andare avanti. Il primo lo osservate se provate ad andare sui canali 4 e 5; vedrete che resta visualizzata lultima immagine mostrata, questo perch noi abbiamo sempre aggiunto la subview ma non labbiamo mai tolta; lo stesso effetto presente quando si spegne il televisore, resta visualizzata lultima immagine. Quindi quello che dobbiamo fare che quando cambiamo canale o quando spegniamo il televisore dobbiamo togliere la sottovista. Nel metodo observeValueForKeyPath:ofObject:change: prima dellistruzione switch che abbiamo inserito per gestire il cambio dei canali aggiungiamo [immagineCanale removeFromSuperview]; questa linvocazione del metodo removeFromSuperview sulloggetto immagineCanale; il suo effetto quello di togliere la UIImageView dalla gerarchia di schermo e quindi non viene pi visualizzata. La stessa istruzione la aggiungiamo nel ramo dove gestiamo lo spegnimento del televisore, cio prima dellistruzione schermo.backgroundColor=[UIColor blackColor]; La versione attuale del metodo sar quindi -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:

(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"acceso"]) { if ([[change valueForKey:@"new"] boolValue]) { schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { [immagineCanale removeFromSuperview]; schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } } else { if ([keyPath isEqualToString:@"canaleSintonizzato"]) { numCanale.selectedSegmentIndex=[ilTelevisore.canaleSintonizzat o intValue]; nomeCanale.text=ilTelevisore.nomeDelCanale; [immagineCanale removeFromSuperview]; switch ([ilTelevisore.canaleSintonizzato intValue]) { case 0: case 1: case 2: immagineCanale.image=[UIImage imageNamed:ilTelevisore.nomeDelCanale]; [schermo addSubview:immagineCanale]; break; default: break; } } else { volume.value=[ilTelevisore.livelloVolume floatValue]; livelloVolume.text = [NSString stringWithFormat:@"%f",volume.value]; } } }

Laltro particolare da sistemare il fatto che quando accendete il televisore lo schermo rimane grigio e non mostra nulla anche se il selettore del canale su 1 e ci aspetteremmo di vedere la scritta Onda1 e limmagine relativa al canale 1; questo avviene perch da nessuna parte nel codice abbiamo detto come aggiornare la visualizzazione al momento dellaccensione. Per risolvere questo problema la cosa migliore utilizzare il codice che abbiamo scritto finora ed attivare il KVO anche al momento dellaccensione. Il modo pi semplice forzare la scrittura del valore nella property canaleSintonizzato in modo da attivare linvocazione del metodo observeValueForKeyPath:ofObject:change:, quindi andiamo in Televisore.m e spostiamo listruzione self.canaleSintonizzato=[NSDecimalNumber zero]; che inizializza la property canaleSintonizzato, dal metodo init al metodo accendi; in tal modo ogni volta che accendiamo il televisore aggiorniamo il valore della property e quindi scateniamo il metodo di osservazione. Passiamo ora a gestire il canale 4; nel quale vogliamo vedere un filmato. Non scopo di questo percorso coprire tutti gli aspetti relativi alla riproduzione di contenuti multimediali, per il loro trattamento completo vi rimando alla documentazione relativa. Prima cosa procuratevi un filmato in formato m4v; rinominatelo Onda4.m4v e trascinatelo dentro la cartella myTv di XCode, ricordatevi di attivare il flag Copy Items La riproduzione di un filmato richiede lutilizzo di un oggetto specializzato (analogamente alluso di una UIImageView per mostrare unimmagine) della classe MPMoviePlayerController. Approfondimento: Studiare sulla documentazione Apple la classe MPMovieController.

Unistanza di questa classe ha una property view, che si riferisce alla view nella quale viene riprodotto il filmato. Lindicazione del filmato da riprodurre gestita da unaltra property, contentURL, che si riferisce ad unistanza di NSURL. Approfondimento: Studiare sulla documentazione Apple la classe NSURL. La classe MPMovieController definita in un altro framework che deve quindi essere aggiunto al progetto. Selezionate il progetto nellalberatura di XCode, nel pannello di dettaglio selezionate il target myTv e andate nel tab Build Phases. Aprite la sezione Link Binary With Libraries e cliccate sul +. Nel pannello che si apre selezionate il framework MediaPlayer.framework. Oltre allaggiunta del framework, nel quale sono definite le classi, abbiamo bisogno anche di rendere noto al codice le dichiarazioni delle classi, quindi in TVController.h aggiungiamo #import <MediaPlayer/MediaPlayer.h> insieme alle altre import. Ora andiamo a dichiarare le due variabili di istanza che ci servono per gestire il player e il nome del filmato. Ancora in TVController.h, allinterno delle {} scriviamo NSURL *movieURL; MPMoviePlayerController *player; cio abbiamo dichiarato due variabili di istanza movieURL e player. Nella prima manterremo il nome del file con il filmato da riprodurre. Nella seconda avremo loggetto per la riproduzione del video. Ora che le abbiamo dichiarate andiamo a definirle. In TVController.m, nel metodo bserveValueForKeyPath:ofObject:change: nello switch/case di gestione dei canali aggiungiamo case 3:

cio il punto di ingresso per la gestione del canale 4. La prima cosa che dobbiamo fare creare unistanza di NSURL per il riferimento al nome de file video, aggiungiamo quindi movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:ilTelevisore.nomeDelCanale ofType:@"m4v"]]; stiamo assegnando alla variabile di istanza movieURL il risultato dellinvocazione del metodo fileURLWithPath della classe NSURL. Questo metodo crea unistanza di NSURL che si riferisce al file il cui percorso passato come argomento. Largomento che passiamo [[NSBundle mainBundle] pathForResource:ilTelevisore.nomeDelCanale ofType:@"m4v"] NSBundle, labbiamo gi incontrata ed una classe che fornisce diverse funzionalit per operare sulla struttura dellapplicazione (vi rimando alla documentazione per il suo approfondimento), in particolare il metodo che stiamo invocando pathForResource, restituisce il percorso del file il cui nome gli passato come argomento, ilTelevisore.nomeDelCanale che vi ricordo in questo momento vale Onda4, ed il cui tipo m4v. Quindi in sostanza assegnamo alla variabile movieURL unistanza di NSURL con il riferimento al nostro filmato. Quello che dobbiamo fare ora creare loggetto player che abbiamo dichiarato come variabile di istanza. Aggiungiamo quindi player =[[MPMoviePlayerController alloc] initWithContentURL:movieURL]; la classica struttura alloc/init per la creazione di istanze con la particolarit che linit di questa classe prevede anche il passaggio dellURL del video da riprodurre. Ora dobbiamo dire quanto grande la vista del player per la riproduzione, loperazione analoga a quello che abbiamo fatto da Interface Builder quando abbiamo ridimensionato la UIImageView per renderla grande

quanto la view schermo. Aggiungiamo player.view.frame=schermo.bounds; Cio assegnamo alla property frame della property view del player le stesse dimensioni della property bounds della vista schermo. Approfondimento: Andate sulla documentazione di UIView e guardate la differenza tra la property frame e la property bounds. A questo punto rendiamo visibile la view del player, come abbiamo fatto per la UIImageView, aggiungendola come sottovista dello schermo, aggiungiamo quindi listruzione [schermo addSubview:player.view]; che aggiunge la view del player nella gerarchia della view schermo. Infine lanciamo la riproduzione del video aggiungendo listruzione [player play]; cio invochiamo il metodo play sulloggetto player che istanza di MPMoviePlayerController. Il metodo play predefinito per questa classe ed ha leffetto di riprodurre il filmato indicato dalla property contentURL. Come abbiamo fatto per la view delle immagini; dobbiamo togliere la view del player dalla gerarchia, quando non ne abbiamo pi bisogno. Andiamo quindi prima dellistruzione switch, dove abbiamo inserito listruzione [immagineCanale removeFromSuperview]; ed aggiungiamo [player.view removeFromSuperview]; La stessa istruzione laggiungiamo nel blocco dove gestiamo levento di spegnimento; cio il ramo else della condizione if ([keyPath isEqualToString:@"acceso"]) La versione corrente del metodo diventa -(void)observeValueForKeyPath:(NSString *)keyPath ofObject:

(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"acceso"]) { if ([[change valueForKey:@"new"] boolValue]) { schermo.backgroundColor=[UIColor lightGrayColor]; interruttore.on=TRUE; } else { [immagineCanale removeFromSuperview]; [player.view removeFromSuperview]; schermo.backgroundColor=[UIColor blackColor]; interruttore.on=FALSE; } } else { if ([keyPath isEqualToString:@"canaleSintonizzato"]) { numCanale.selectedSegmentIndex=[ilTelevisore.canaleSintonizzat o intValue]; nomeCanale.text=ilTelevisore.nomeDelCanale; [immagineCanale removeFromSuperview]; [player.view removeFromSuperview]; switch ([ilTelevisore.canaleSintonizzato intValue]) { case 0: case 1: case 2: immagineCanale.image=[UIImage imageNamed:ilTelevisore.nomeDelCanale]; [schermo addSubview:immagineCanale]; break; case 3: movieURL = [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:ilTelevisore.nomeDelCanale ofType:@"m4v"]]; player =[[MPMoviePlayerController alloc] initWithContentURL:movieURL]; player.view.frame=schermo.bounds; [schermo addSubview:player.view];

[player play]; break; default: break; } } else { volume.value=[ilTelevisore.livelloVolume floatValue]; livelloVolume.text = [NSString stringWithFormat:@"%f",volume.value]; } } } Eseguiamo lapp e verifichiamo che i canali da 1 a 4 siano correttamente visualizzati. Passiamo ora a gestire la visualizzazione del canale 5, nel quale abbiamo deciso che vogliamo visualizzare un sito. Prima cosa diciamo quale sito vogliamo visualizzare; andiamo in Televisore.m e nel metodo ricercaCanali, dove inizializziamo la property canaliMemorizzati con i valori Onda1, Onda2,Onda3, sostituiamo Onda5 con http://www.apple.com o con un altro indirizzo a vostra scelta. La visualizzazione di un sito web allinterno di un app gestita da una classe specifica UIWebView, cos come per la visualizzazione di immagini si usa un UIImageView e per la visualizzazione di un video si usa un view inclusa in un MPMoviePlayerController. Unistanza di UIWebView sar quindi una view che metteremo come subview della view schermo e che sar in grado di visualizzare un sito web. Nei casi precedenti abbiamo visto due modalit diverse di creazione delle view di gestione dei contenuti. Nel primo caso, la visualizzazione di immagini, abbiamo creato la view da Interface Builder e da codice abbiamo solo gestito linserimento e la rimozione dalla gerarchia. Nel secondo caso invece abbiamo

anche creato la view da codice, anche se implicitamente con la creazione di un controller. Queste sono due modalit che quando sono entrambe percorribili sono entrambe corrette. Nel caso della WebView utilizzeremo nuovamente la strada di creare tutto da codice. Iniziamo con il dichiarare le due variabili di istanza che useremo. In TVController.h dentro le {} aggiungiamo NSURL *sitoURL; UIWebView *myWebView; La prima si riferir ad unistanza di NSURL che conterr lindirizzo del sito da visualizzare, mentre la seconda conterr listanza di UIWebView responsabile della visualizzazione. Ora andiamo nel metodo observe e allinterno dello switch nel quale stiamo gestendo i cambi di canale, prima di default: aggiungiamo il punto di ingresso per lopzione 4. case 4: La prima cosa che dobbiamo fare a questo punto creare loggetto NSURL con lindirizzo del sito sitoURL=[NSURL URLWithString:ilTelevisore.nomeDelCanale]; alla nostra variabile di istanza sitoURL assegnamo il risultato dellinvocazione del metodo di classe URLWithString, questo metodo restituisce unistanza di NSURL che rappresenta unindirzzo che gli viene passato in ingresso come oggetto NSString. Il nostro NSString ilTelevisore.nomeDelCanale che restituisce il valore @http:// che abbiamo scritto nel metodo ricercaCanali del televisore. Poi creiamo listanza della nostra web view aggiungendo myWebView = [[UIWebView alloc] init]; usiamo la coppia alloc/init per creare unistanza di UIWebView ed assegnarla alla nostra variabile di istanza. Ora configuriamo la nostra view in modo da darci una visione il

pi possibile completa del sito, anche se piccola, aggiungendo myWebView.scalesPageToFit=YES; la property scalesPageToFit ridimensiona la pagina in modo da visualizzarne la larghezza dentro la view. Diamo alla webView la stessa dimensione dello schermo myWebView.frame=schermo.bounds; Chiediamo alla webView di caricare il nostro sito [myWebView loadRequest:[NSURLRequest requestWithURL:sitoURL]]; Il metodo loadRequest ha leffetto di caricare nella webView che lo esegue lURL che gli viene passato come argomento. Largomento passato come istanza di NSURLRequest, quindi necessario creare unistanza di NSURLRequest a partire dal nostro oggetto NSURL, attraverso il metodo requestWithURL che prende in ingresso il nostro oggetto NSURL sitoURL e restituisce un oggetto di NSURLRequest che viene passato a loadRequest. Infine visualizziamo la webView inserendola nella gerarchia di schermo [schermo addSubview:myWebView]; Anche in questo caso , come nei precedenti, dobbiamo togliere la view dalla gerarchia con [myWebView removeFromSuperview]; prima dellistruzione switch e nel ramo in cui gestiamo lo spegnimento del televisore. Eseguiamo lapp e guardiamo i nostri canali. Prima di concludere facciamo unottimizzazione. Abbiamo visto che ogni volta che abbiamo aggiunto una nuova view ci siamo sempre preoccupati di rimuoverla con le diverse istruzioni removeFromSuperview. Per abbiamo dovuto ripetere pi volte la stessa istruzione trattandosi di oggetti diversi. Vediamo come

possiamo ottimizzare questo aspetto attraverso luso dei tag. Ogni oggetto in Objective-C ha un tag che un numero intero che gli pu essere assegnato, vediamo com epossiamo usarlo per ottimizzare il nostro codice. Quindi assegnamo alle nostre view sempre il tag 100. Andate in TelevisoreView.xib e selezionate loggetto Image View. NellAttributes Inspector alla sezione View, scrivete 100 nel campo tag. In TVController.m nel metodo observe, dopo listruzione player.view.frame=schermo.bounds; aggiungete player.view.tag=100; Dopo listruzione myWebView.scalesPageToFit=YES; aggiungete myWebView.tag=100; Bene, ora abbiamo le nostre tre view che hanno tutte il tag 100. Quindi sostituiamo le tre istruzioni [... removeFromSuperview] che abbiamo inserito prima dello switch e nel ramo di spegnimento con [[schermo viewWithTag:100] removeFromSuperview]; Con [schermo viewWithTag:100] otteniamo la subview di schermo che ha tag 100; su di essa invochiamo il removeFromSuperview per rimuoverla dalla gerarchia, in questo modo possiamo fare tutto con ununica istruzione anche se sono oggetti diversi. Eseguiamo ancora una volta lapp per verificare che funzioni tutto. In questo capitolo abbiamo introdotto diversi oggetti che si

possono usare per visualizzare diversi tipi di contenuto, ovviamente non possibile in un corso di queste dimensioni trattare approfonditamente tutto il trattamento di contenuti multimediali e web, per cui vi rimando alla documentazione specifica per unanalisi pi approfondita. Abbiamo per visto come si gestisce la gerarchia di view in modo da cambiare i contenuti mostrati in una interfaccia utente. Abbiamo visto le due modalit di gestione delle gerarchie, cio creando le view da Interface Builder o creandole da codice ed abbiamo visto un possibile utilizzo del tag delle viste. In allegato potete scaricare il progetto aggiornato a questo capitolo. 19 Aggiungiamo una TableView
iOS, Mac, OSX, SDK, Tech Room

In questo capitolo iniziamo a parlare di uno degli elementi pi usati nelle app per iOS: la TableView. E la struttura di interfaccia che si usa per presentare i dati in forma di elenco; quella che usate quando accedete allapp Contatti o Impostazioni. La TableView si pu presentare in diversi modi e pu anche essere personalizzata, per cui una volta che lavremo usata nel nostro programma e quindi che vi avr illustrato i concetti base, sar necessario che guardiate la documentazione per approfondire tutte le opportunit offerte da questo tipo di oggetto. Al nostro televisore con telecomando aggiungiamo una funzionalit di video-registratore. Vogliamo avere la possibilit di programmare delle registrazioni associate ad uno dei nostri canali. Ad ogni programmazione saranno poi associate le diverse registrazioni effettuate.

Iniziamo con MVC. Model Quali informazioni vogliamo gestire? Programmazione. Ha le propriet Stringa con il nome del canale da registrare; Stringa con il nome del programma da registrare; Elenco delle registrazioni associate a quella programmazione e le funzioni aggiungi una registrazione della programmazione Elenco delle programmazioni Con le tipiche operazioni di elenco: inserimento e cancellazione. Registrazione Programmazione associata Data/ora della registrazione. Scriviamo la classe per modellare una programmazione. Clicchiamo con il bottone destro sul gruppo myTv dellalbero di XCode e scegliamo New File. Nel gruppo di template Cocoa Touch scegliamo Objective-C class. Nel pannello successivo scegliamo NSObject come superclasse, infine diamo il nome alla nostra classe chiamandola Programmazione. Vengono creati i due file header (Programmazione.h) ed implementation (Programmazione.m). Selezioniamo il file Programmazione.h e dichiariamo le property che ci servono, inserendo @property(nonatomic,retain) NSString *canaleDaRegistrare; @property(nonatomic,retain) NSString *nomeDelProgramma; @property(nonatomic,retain) NSMutableArray

*registrazioniAssociate; Abbiamo la property canaleDaRegistrare di tipo NSString, per modellare la stringa con il nome del programma. La property nomeDelProgramma una NSString per modellare la stringa con il nome del canale. La property registrazioniAssociate di tipo NSMutableArray, la classe per gestire gli elenchi modificabili di oggetti. Approfondimento: Nella documentazione Apple studiate la classe NSMutableArray. Nel file implementation aggiungiamo innanzitutto le synthesize per le properties @synthesize canaleDaRegistrare; @synthesize nomeDelProgramma; @synthesize registrazioniAssociate; Per quanto riguarda la modellazione dellelenco di programmazioni utilizzeremo la classe predefinita NSMutableArray, per cui non abbiamo bisogno di creare nuove classi. Dobbiamo per dichiarare questo elenco allinterno del nostro Televisore. Andiamo in Televisore.h ed aggiungiamo @property(nonatomic,retain) NSMutableArray *elencoProgrammazioni; nellimplementation scriviamo la relativa synthesize e nel metodoo init aggiungiamo listruzione per creare listanza da associare. self.elencoProgrammazioni=[[NSMutableArray alloc] init]; Infine modelliamo la classe per gestire le registrazioni. Aggiungiamo quindi al nostro progetto una classe, sottoclasse di NSObject, che chiamiamo Registrazione, ottenendo i due file header ed implementation. Nel file header (Registrazione.h) aggiungiamo le properties @property (nonatomic,retain) NSDate *dataOraRegistrazione;

@property (nonatomic,retain) Programmazione *programmazioneAssociata; Come abbiamo visto gi altre volte, stiamo utilizzando una nuova classe (Programmazione) e abbiamo quindi bisogno di farla conoscere aggiungendo #import "Programmazione.h" Ora che abbiamo dichiarato le tre classi torniamo nel file Programmazione.h e aggiungiamo la dichiarazione del metodo da eseguire ogni volta che viene eseguita la programmazione (e quindi si genera una nuova registrazione) e che aggiunge un elemento nellelenco delle registrazioni associato. -(void)addRegistrazioniAssociateObject:(Registrazione *)laRegistrazione; Avremo quindi un metodo che prende in ingresso un oggetto di tipo Registrazione e il suo compito sar quello di inserirlo nellelenco delle registrazioni associate alla programmazione. Come abbiamo visto gi diverse volte per poter usare il nome Registrazione necessario farlo conoscere al codice; quello che facciamo di solito inserire #import "Registrazione.h" Ma questa volta se osserviamo il file Registrazione.h vediamo che l abbiamo inserito limport di Programmazione quindi se facessimo qui limport di Registrazione creeremmo un ciclo di import che XCode non consente. Invece di #import "Registrazione.h" aggiungiamo @class Registrazione; con questa istruzione ci limitiamo a dire che useremo riferimenti alla classe Registrazione; ma, a differenza di quanto accede con limport, il codice non sa come fatta la classe; non potremo quindi far riferimento alle sue property o metodi. Andiamo nellimplementation file (Programmazione.m) e

definiamo il metodo addRegistrazioniAssociateObject:. Quello che dobbiamo fare in questo metodo stabilire la relazione tra listanza di Programmazione che lo esegue e quella di Registrazione che gli viene passata come parametro. Aggiungiamo quindi nellimplementation file - (void)addRegistrazioniAssociateObject:(Registrazione *)laRegistrazione { [self.registrazioniAssociate addObject:laRegistrazione]; laRegistrazione.programmazioneAssociata=self; } sulla istanza di NSMutableArray referenziata dalla property registrazioniAssociate invochiamo il metodo addObject passandogli listanza laRegistrazione, il cui effetto proprio quello di aggiungere quella istanza allelenco. Inoltre assegnamo alla property programmazioneAssociata di laRegistrazione listanza self. In questo modo abbiamo costruito i due versi della relazione; da una programmazione possiamo accedere a tutte le sue registrazioni e da una registrazione possiamo accedere alla programmazione relativa. Ma per fare questa operazione abbiamo bisogno di accedere ad un aproperty di registrazione e siccome non labbiamo importata, ma labbiamo solo dichiarata con @class, XCode ci segnala un errore. A questo punto andiamo in Programmazione.m ed aggiungiamo #import "Registrazione.h" Riprendiamo listruzione [self.registrazioniAssociate addObject:laRegistrazione]; Abbiamo detto che questa istruzione aggiunge un oggetto ad un elenco referenziato da registrazioniAssociate. Ma noi non abbiamo creato alcun elenco. E necessario quindi andare nel metodo init di Programmazione (nel file implementation), che, vi ricordo, il metodo invocato ogni volta che viene creato unistanza di una classe, e modificarlo come segue

- (id)init { self = [super init]; if (self) { // Initialization code here. self.registrazioniAssociate=[[NSMutableArray alloc] init]; } return self; } cio allinterno del blocco if (self) {} abbiamo aggiunto listruzione self.registrazioniAssociate=[[NSMutableArray alloc] init]; con la quale, con il pattern alloc/init, creiamo unistanza della classe NSMutableArray e la assegnamo alla nostra property. View Dal punto di vista dellInterfaccia Utente aggiungeremo un tab alla TabBar per gestire le programmazioni ottenendo qualcosa del tipo

Quando cliccheremo sul bottone + si aggiunger una nuova programmazione che caratterizzeremo attraverso la view

Quando selezioneremo una riga della TableView apriremo una view nella quale possiamo modificare le informazioni e, siccome non stiamo introducendo un timer, simulare lavvio di una registrazione.

Riepilogando avremo una navigazione, come questa

Apriamo il file MainWindow.xib. Nella sezione Objects del frame di sinistra esplodiamo lalberatura delloggetto Tab Bar Controller. Nella library degli oggetti disponibili, sezione inferiore del frame di destra prendete un oggetto Navigation Controller e trascinatelo nellalberatura del Tab Bar, fate attenzione che sia parte dellalberatura e non sia esterno. Selezionate il nuovo Tab Bar Item e nellAttributes Inspector cambiate il titolo in Programmazioni, oppure potete agire direttamente sullelemento grafico con un doppio click. Approfondimento: nella documentazione Apple cercate e studiate la classe UINavigationController. Andiamo ora a creare lo xib con la TableView. Cliccando con il bottone destro sul gruppo myTv di XCode

scegliete New File e nel gruppo User Interface della sezione iOs scegliete View, indicate iPhone come device target e date un nome al file (ProgrammazioniTableView). Ora, con il nuovo file aperto, cancellate loggetto View nella sezione Objects del frame di sinistra e trascinateci un oggetto Table View preso dalla Library del frame di destra. Creiamo ora la View per i dettagli della programmazione, ne creeremo una sola e gestiremo da codice la presenza o mento del bottone Registra. Aggiungiamo al nostro progetto un nuovo xib di tipo View (come abbiamo fatto sopra) e chiamiamolo DettagliProgrammazioneView. Prendiamo una Label dalla Library e posizioniamola sulla View. Con un doppio click sulla Label cambiamo il testo in Nome del Programma. Dalla Library prendiamo un TextField e posizioniamolo sotto la Label, e allarghiamone un po la dimensione. Prendiamo unaltra Label, posizioniamola sotto il Text Field e scriviamoci Canale. Prendiamo un Segmented Control e posizioniamolo sotto la Label. Nel Attributes Inspector indichiamo che ha 5 segmenti; clicchiamo su ogni segmento e scriviamoci i numeri da 1 a 5; ridimensionatelo opportunamente. Prendiamo un Round Rect Button dalla Library e posizioniamolo sulla View, con un doppio click scriviamoci Registra. Alla fine dovreste avere qualcosa del tipo

Controller Iniziamo a definire il controller della Table View. Una Table View unistanza della classe UITableView che espone tutti i metodi necessari per laccesso alle informazioni presenti nella tabella. Una riga di una UITableView costituita da unistanza di UITableViewCell; quindi per visualizzare delle informazioni in una riga di una UITableView sar necessario istanziare unoggetto di UITableViewCell. Per gestire una tabella, oltre ai metodi di UITableView, sono necessari altri due set di metodi, uno per il popolamento della tableView (metodi di datasource) e uno per la gestione degli eventi su tableView (metodi di delegate). Sar quindi necessario assegnare ad uno o pi oggetti del nostro programma il ruolo di delegate e datasource per la tableview, questi oggetti implementeranno i relativi metodi.

Approfondimento: studiate sulla documentazione la classe UITableView ed i protocolli UITableViewDelegate e UITableViewDatasource. Creiamo la classe per il nostro controller. Aggiungiamo un file al nostro progetto e scegliamo, nel gruppo Cocoa Touch, il tipo UIViewController subclass. Nella finestra successiva scegliamo UITableViewController come superclasse e chiamiamola ProgrammazioniTableViewController. Se scorriamo il file ProgrammazioniTableViewController.m vediamo che ci sono due sezioni #pragma una per i metodi datasource e una per i metodi delegate, questo perch il template UITableViewController proposto da Apple prevede che la classe creata istanzi oggetti che avranno anche il ruolo di datasource e delegate. Il nostro controller, come abbiamo detto nellintroduzione al pattern MVC, dovr collegare gli oggetti del Model agli oggetti di View. Come abbiamo fatto nei casi precedenti dovremo quindi definire delle opportune properties per gestire questi legami. Il collegamento verso View deve associare il controller alla tableview, la classe UITableViewController, di cui la nostra classe sottoclasse, espone gi una property (dichiarata come IBOutlet e quindi collegabile ad oggetti di xib) chiamata tableview adatta a questo scopo che viene ereditata. Il collegamento verso Model deve collegare il controller con le informazioni da visualizzare nella tableview, cio, nel nostro caso, con lelenco delle programmazioni che gestito allinterno della classe Televisore. Apriamo il file ProgrammazioniTableViewController.h e aggiungiamo allinterno della definizione della classe @property (nonatomic, retain) Televisore *ilTelevisore; e nellimplementation file la relativa synthesize.

Passiamo ad implementare i metodi di datasource, quelli che servono a popolare correttamente la tableview. La prima informazione di cui il sistema ha bisogno sapere quante sezioni ha la tableview. Il concetto di sezione quello che potete vedere nellapp contatti nella quale i nomi sono raggruppati per lettera iniziale. Ogni iniziale individua una diversa sezione della tableview. Nel nostro caso noi avremo un unico elenco di programmazioni non raggruppate quindi avremo una sola sezione. Andiamo nel metodo - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView che trovate nella sezione datasource e sostituiamo return 0; con return 1; Questo metodo viene invocato dal sistema quando ha bisogno di disegnare la tabella per sapere quante sezioni ha; noi stiamo dicendo che avr una sola sezione. Se necessario (dipende dalla versione di XCode che state utilizzando) rimuovete la direttiva #warning che trovate allinizio del metodo. Poi il sistema ha bisogno di sapere quante righe ci sono per ogni sezione; lo fa invocando il metodo - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section a cui passa come argomento il numero della sezione e si aspetta come risultato il numero di righe della sezione. Quindi questo metodo viene invocato tante volte quante sono le sezioni della tabella risultato del metodo precedente.

Quanto righe avr la nostra tabella? Deve scrivere una riga per ogni programmazione quindi saranno tante quante le programmazioni, cio tante quanti gli oggetti presenti nellelenco elencoProgrammazioni. Quindi sostituiamo return 0; con return [self.ilTelevisore.elencoProgrammazioni count]; cio restituiamo il risultato dellinvocazione del metodo count sulla nostra istanza di NSMutableArray e questo metodo restituisce esattamente il numero di oggetti presenti. Anche qui se necessario rimuovete la direttiva #warning. Lultima informazione obbligatoria che il datasource deve fornire quella relativa al contenuto di ogni cella; questo si ottiene con linvocazione del metodo - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath che usa come parametro unistanza di NSIndexPath. Questa classe viene usata per modellare una coppia di numeri accedibili attraverso le properties section e row, con le quali possibile individuare univocamente la cella per la quel bisogna fornire il contenuto. Se analizziamo questo metodo vediamo che c gi una parte di codice predisposto automaticamente, questa parte serve a creare listanza di UITableViewCell che sar restituita e quindi usata per costruire la tableview. Non entro nel merito di questa parte di codice e vi rimando alla documentazione per capire appieno il suo significato; lunica considerazione che facciamo relativa allargomento initWithStyle:UITableViewCellStyleDefault questo parametro inizializza una cella con un tipo predefinito, questo indicato nel codice fornito la classica cella con una riga di testo.

Ma noi abbiamo detto che vogliamo una riga per indicare il programma ed una riga per indicare il canale; cambiamo quindi UITableViewCellStyleDefault con UITableViewCellStyleSubtitle che costruisce una cella con due righe come vogliamo noi. Una volta che stata costruita la cella dobbiamo riempirla con il contenuto. Ad ogni riga della tabella corrisponder un elemento dellelenco delle programmazioni, cio la prima riga mostrer le informazioni della prima programmazione, la seconda riga quelle della seconda programmazione, e cos via. Ricordando che allinterno di questo metodo disponibile lindexPath la cui property row indica a quale riga si riferisce la cella che dobbiamo restituire prima di return cell; aggiungiamo cell.textLabel.text=((Programmazione *) [self.ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row]).nomeDelProgramma; a sinistra abbiamo cell.textLabel.text. cell listanza di UITableViewCell che abbiamo appena creato allinizio del metodo e che restituiremo per costruire la tableview. Accediamo alla property textLabel di questa istanza che la UILabel che mostra il testo principale della cella. Infine accediamo alla property text della UILabel per assegnare il testo da mostrare. Con indexPath.row accediamo alla property row di indexPath che indica il numero di riga per la quale dobbiamo restituire la cella che stiamo costruendo; con [self.ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row] invochiamo il metodo objectAtIndex sullistanza

elencoProgrammazioni passandogli largomento indexPath.row. Questo metodo restituisce loggetto presente nella posizione indicata da indexPath.row nellelenco elencoProgrammazioni, questo oggetto sar unistanza della nostra classe Programmazioni quindi possiamo accedere alla property nomeDelProgramma per farci restituire la stringa da scrivere nella cella. [self.ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row].nomeDelProgramma Per far si che XCode conosca la property nomeDelProgramma, dobbiamo fare il type casting per indicare che loggetto restituito istanza di Programmazione ((Programmazione *)[self.ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row]).nomeDelProgramma Come abbiamo fatto pi volte per far usare la classe Programmazione allinterno di questo file, andiamo in testa ed aggiungiamo #import "Programmazione.h" Quindi con questa istruzione assegnamo alla prima cella il nome del programma della prima programmazione, alla seconda il nome della seconda programmazione e cos via. Scriviamo anche la seconda riga della nostra cella, aggiungendo cell.detailTextLabel.text=((Programmazione *) [self.ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row]).canaleDaRegistrare; qui accediamo alla property detailTextLabel che si riferisce alla seconda riga di testo della cella, la logica del termine a destra la stessa appena discussa. Bene, abbiamo definito i tre metodi base per le tabelle; abbiamo detto quante sono le sezioni, quante righe ha ogni sezione e qual il contenuto di ogni riga. Andiamo ora a collegare le nostre classi agli oggetti dei nostri xib.

Apriamo il file ProgrammazioniTableView.xib, selezioniamo il Files Owner e nel Identity Inspector nel campo Class selezioniamo ProgrammazioniTableViewController. Con il bottone destro premuto colleghiamo la tableview con il Files owner selezionando datasource una volta e delegate laltra. Con il bottone estro premuto colleghiamo il Files owner con la table view e scegliamo loutlet view. Apriamo il file MainWindow.xib, esplodiamo la gerarchia Tab Bar Controller ed al suo interno esplodiamo la gerarchia del Navigation Controller. Selezioniamo il View Controller al suo interno e nel Identity Inspector assegnamogli la classe ProgrammazioniTableViewController. Nel Attributes Inspector nel campo NIB Name scriviamo ProgrammazioniTableView. Completiamo i collegamenti, collegando il controller con listanza del televisore. Andiamo in myTvAppDelegate.h e dichiariamo una property per collegare il controller al codice @property (nonatomic, retain) IBOutlet ProgrammazioniTableViewController *myProgController; Com al solito per poter usare la classe ProgrammazioniTableViewController dobbiamo aggiungere la import #import "ProgrammazioniTableViewController.h" Non dimentichiamo di aggiungere la synthesize nellimplementation file. Colleghiamo ora la property appena creata alloggetto nello xib. Apriamo il file MainWindow.xib, con il bottone destro premuto colleghiamo loggetto My Tv App Delegate al View Controller dentro al Navigation Controller e scegliamo lOutlet myProgController. Infine, torniamo al file myTvAppDelegate.m ed andiamo al

metodo (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions dopo listruzione myTVController.ilTelevisore=tv1; aggiungiamo myProgController.ilTelevisore=tv1; abbiamo assegnato alla property ilTelevisore del controller della tableview listanza della classe Televisore che abbiamo appena creato nel codice. Bene, abbiamo completato tutti i collegamenti, ripercorreteli analizzandoli bene. Ora per provare il funzionamento dell nostro programma, popoliamo con dei dati la struttura dellelenco delle programmazioni. Andiamo nel file ProgrammazioniTableViewController.m e andiamo nel metodo viewDidLoad. Questo metodo viene eseguito quando la view associata al controller viene caricata dal file xib. Modifichiamolo cos - (void)viewDidLoad { [super viewDidLoad]; Programmazione * programma=[[Programmazione alloc] init]; programma.nomeDelProgramma=@"Film 1"; programma.canaleDaRegistrare=@"Onda1"; [self.ilTelevisore.elencoProgrammazioni addObject:programma]; programma=[[Programmazione alloc] init]; programma.nomeDelProgramma=@"Documentario 1"; programma.canaleDaRegistrare=@"Onda2"; [self.ilTelevisore.elencoProgrammazioni addObject:programma]; programma=[[Programmazione alloc] init];

programma.nomeDelProgramma=@"Film 2"; programma.canaleDaRegistrare=@"Onda3"; [self.ilTelevisore.elencoProgrammazioni addObject:programma]; // Uncomment the following line to preserve selection between presentations. // self.clearsSelectionOnViewWillAppear = NO; // Uncomment the following line to display an Edit button in the navigation bar for this view controller. // self.navigationItem.rightBarButtonItem = self.editButtonItem; } Abbiamo aggiunto tre blocchi di istruzioni. programma=[[Programmazione alloc] init] creiamo unistanza di Programmazione e la assegnamo alla variabile programma (viene dichiarata nella prima istruzione contestualmente allassegnamento). programma.nomeDelProgramma=@"..."; programma.canaleDaRegistrare=@"..."; assegnamo dei valori alle properties dellistanza programma. [self.ilTelevisore.elencoProgrammazioni addObject:programma]; aggiungiamo loggetto creato allelenco delle programmazioni. Ora eseguiamo il programma e verifichiamo il corretto funzionamento del tab Programmazioni.

20 Gestire la Tabella
iOS, Mac, OSX, SDK, Tech Room

Riprendiamo la gestione della nostra TableView vedendo come possiamo inserire e cancellare elementi ricordando sempre che la TableView solo la componente View di un Model costituito da elencoProgrammazioni che dovr sempre essere allineato con la visualizzazione. Rispetto a quanto abbiamo disegnato nel cap. precedente invece del bottone con + che avremmo usato per aggiungere una programmazione useremo un bottone di edit che useremo sia per aggiungere che per cancellare le programmazioni. Andiamo nel file ProgrammazioniTableViewController.m e nel metodo viewDidLoad aggiungiamo self.navigationItem.title=@"Programmazioni"; self.navigationItem.rightBarButtonItem=[self editButtonItem]; entrambe queste istruzioni accedono alla property navigationItem che viene valorizzata automaticamente dal sistema quando un UIView Controller inserito nella gerarchia di un Navigation Controller e contiene il riferimento alla barra di navigazione. Con la prima istruzione accediamo poi alla property title della barra e le assegnamo @Programmazioni, questo il titolo che sar visualizzato nella barra. Con la seconda istruzione accediamo alla property rightBarButtonItem che si riferisce al bottone pi a destra nella barra di navigazione e le assegnamo il risultato dellinvocazione [self editButtonItem]; questo un metodo predefinito che restituisce un bottone che cambia stato (edit/done) e che attiva lediting della tabella. Proviamo ad eseguire e osserviamo il comportamento del bottone edit. Quello che accade il bottone passa da edit a done e viceversa in modalit edit a sinistra di ogni etichetta di cella appare un

segno in campo rosso cliccando il a destra di ogni cella appare un bottone delete. Fra un po vedremo come gestire la cancellazione, intanto riflettiamo su una cosa: se questo il modo per andare in edit sulla tabella come facciamo a dire che vogliamo aggiungere una riga? La soluzione che adottiamo quella di usare una riga vuota come riga di inserimento, questo si tradurr nellinterfaccia con il fatto che in modalit edit apparir un + in campo verde sulla prima riga vuota della tabella. La prima cosa da fare dire al sistema che la tabella ha una riga in pi rispetto a quelle dellelenco, in modo da avere sempre una riga libera. Cambiamo il metodo che stituisce il numero di righe come segue - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { // Return the number of rows in the section. return [self.ilTelevisore.elencoProgrammazioni count]+1; } cio abbiamo aggiunto 1 al valore restituito. Poi dobbiamo andare nel metodo cellForRowAtIndexPath per dire come dovr essere costruita anche lultima cella (cio la prima vuota). Racchiudiamo le istruzioni di personalizzazione delle celle con le informazioni in un test di verifica se siamo nelle prime righe, quelle per cui abbiamo un elemento corrispondente dellelenco if (indexPath.row<[self.ilTelevisore.elencoProgrammazioni count]) { cell.textLabel.text=((Programmazione *) [self.ilTelevisore.elencoProgrammazioni

objectAtIndex:indexPath.row]).nomeDelProgramma; cell.detailTextLabel.text=((Programmazione *) [self.ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row]).canaleDaRegistrare; } quindi solo le celle associate ad un elemento di elencoProgrammazioni avranno qualcosa scritto dentro mentre lultima cella rester bianca. Eseguiamo il programma e verifichiamo che la riga vuota sia gestita correttamente, clicchiamo sul bottone edit e verifichiamo che appaia anche nella riga bianca il segnale di edit. Osserviamo per che anche nella riga vuota appare il in campo rosso mentre noi vogliamo il simbolo +. Per fare questo dobbiamo implementare il metodo (UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath questo un metodo standard del delegate di UITableView che prende in ingresso un indice della tableView e restituisce lo stile di editing della cella nella posizione individuata da quellindice. Scriviamo quindi nel file ProgrammazioniTableViewController.m -(UITableViewCellEditingStyle)tableView:(UITableView *)tableView editingStyleForRowAtIndexPath:(NSIndexPath *)indexPath { if (indexPath.row==[self.ilTelevisore.elencoProgrammazioni count]) { return UITableViewCellEditingStyleInsert; } else { return UITableViewCellEditingStyleDelete; } } cio controlliamo se stato invocato per una delle celle con associato un elemento dellelenco, in questo caso restituiamo il

valore UITableViewCellEditingStyleDelete e sar visualizzato il -, altrimenti restituiamo UITableViewCellEditingStyleInsert e sar visualizzato il +. Eseguiamo lapp e verifichiamo la corretta visualizzazione dei bottoni. Bene, ora che abbiamo i due elementi di interfaccia (I pulsanti delete sulle righe con dati e il pulsante + sulla riga vuota) ci chiediamo cosa accade quando ci clicchiamo sopra. Il sistema invoca un metodo delloggetto delegato, in particolare invoca (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath: (NSIndexPath *)indexPath passandogli sia leditingStyle, in modo da capire se dobbiamo cancellare o inserire, che lindice della riga cliccata. Andiamo ora a gestire i due eventi di cancellazione ed inserimento. Scorrendo il file arriviamo al metodo /// Override to support editing the table view. - (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath: (NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // Delete the row from the data source [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; } else if (editingStyle == UITableViewCellEditingStyleInsert) { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view }

che viene fornito automaticamente da XCode ma commentato. Togliamo i simboli /* e */ che troviamo subito prima e subito dopo il metodo e che, vi ricordo, servono per indicare al compilatore che la parte di codice compresa da ignorare in quanto commento. In questo modo rendiamo operativo il metodo allinterno del codice. La struttura del metodo semplice, si controlla se dobbiamo cancellare lelemento passato come parametro o se ne dobbiamo inserire uno nuovo e si esegue il blocco di codice corrispondente. Nel blocco relativo alla cancellazione gi fornita listruzione per cancellare la riga dalla tabella, deve invece essere implementata la cancellazione dellelemento corrispondente nella struttura del Model (elencoProgrammazioni). Nel blocco insert invece tutto da sviluppare. Sappiamo dunque che avremo bisogno di inserire e cancellare elementi dallelenco delle programmazioni. Poich questo elenco esposto come property da ilTelevisore potremmo agire direttamente dal controller sullelenco con i metodi tipici di NSMutableArray, ma per pulizia di codice preferibile scrivere due metodi ad hoc nella classe Televisore, che gestisce lelenco, ed invocarli secondo le nostre necessit. In Televisore.h aggiungiamo la dichiarazione dei due metodi -(void)cancellaProgrammazione:(NSInteger)posizione; -(void)inserisciProgrammazione:(Programmazione *)nuovoProgramma; Il primo riceve in ingresso un numero intero e canceller lelemento di elencoProgrammazioni in quella posizione. Il secondo riceve in ingresso unistanza di Programmazione e la inserir in elencoProgrammazione. Come abbiamo gi incontrato tante volte per usare la classe Programmazione ne dobbiamo importare il file header, aggiungiamo quindi #import "Programmazione.h"

Andiamo in Televisore.m ed implementiamo i due metodi. -(void)cancellaProgrammazione:(NSInteger)posizione { [self.elencoProgrammazioni removeObjectAtIndex:posizione]; } -(void)inserisciProgrammazione:(Programmazione *)nuovoProgramma { [self.elencoProgrammazioni addObject:nuovoProgramma]; } vi lascio come esercizio lo studio dei due metodi. Ora che abbiamo i nostri metodi di utilit, torniamo al metodo di gestione degli eventi - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath ed implementiamo la cancellazione // Override to support editing the table view. - (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath: (NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { // Delete the row from the data source [self.ilTelevisore cancellaProgrammazione:indexPath.row]; [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; } else if (editingStyle == UITableViewCellEditingStyleInsert) { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view } } Abbiamo due istruzioni:

[self.ilTelevisore cancellaProgrammazione:indexPath.row]; invoca il metodo che abbiamo appena definito nella classe Televisore al quale passiamo lindice della riga selezionata che corrisponde allindice dellelemento da cancellare dallelenco. [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; linvocazione di un metodo predefinito di UITableView che prende in ingresso un elenco di posizioni e le cancella dalla tableView con un effetto di animazione. Lelenco di posizioni da cancellare che gli passiamo costituito dalla sola posizione selezionata [NSArray arrayWithObject:indexPath] Eseguiamo il programma e verifichiamo che la cancellazione avvenga correttamente. Passiamo a gestire linserimento. La prima cosa che dobbiamo fare allineare il Model, cio dobbiamo creare unistanza di Programmazione ed aggiungerla allelenco delle programmazioni. Nel blocco di gestione dellevento di selezione del bottone + else if (editingStyle == UITableViewCellEditingStyleInsert) { // Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view } aggiungiamo Programmazione *nuovoProgramma=[[Programmazione alloc] init]; il solito pattern di creazione delle istanze e quindi non mi dilungo. Aggiungiamo allelenco del televisore la programmazione appena creata con listruzione

[self.ilTelevisore inserisciProgrammazione:nuovoProgramma]; Ora dobbiamo dare le informazioni relative alla programmazione appena creata. Ricordiamo che abbiamo creato uno xib con linterfaccia dei dettagli di una programmazione; quindi abbiamo il Model (la nuova istanza di Programmazione), la View (lo xib con la vista per i dettagli), ci manca il Controller. Aggiungiamo al nostro progetto un nuovo file sottoclasse di UIViewController (immagino che a questo punto sappiate come fare) e chiamiamolo DettagliProgrammazioneViewController. Seguendo il Pattern MVC, colleghiamo questo controller con il Model dichiarando allinterno del header le properties @property (nonatomic,retain) Programmazione *laProgrammazione; @property (nonatomic,retain) Televisore *ilTelevisore; e definiamone la synthesize nel implementation. Come al solito, per poter usare le classi Programmazione e Televisore dobbiamo fare la import in testa al file. Per il collegamento con il livello View, osserviamo che noi avremo bisogno di leggere il valore di un textfield, il nome del programma, lo stato di un segmented control ed infine dovremo rispondere ad un bottone per effettuare la registrazione del programma. Avremo quindi due outlet ed una action, il file header quindi sar @interface DettagliProgrammazioneViewController : UIViewController { IBOutlet UITextField *nomeProgramma; IBOutlet UISegmentedControl *numeroCanale; } @property (nonatomic,retain) Programmazione

*laProgrammazione; @property (nonatomic,retain) Televisore *ilTelevisore; -(IBAction)registra:(id)sender; @end Nel file implementation aggiungiamo la definizione del metodo registra del quale scriveremo la logica pi avanti. -(IBAction)registra:(id)sender { } Ora andiamo in Interface builder ed effettuiamo i collegamenti. Apriamo il file DettagliProgrammazioneView.xib, selezioniamo il Files owner e, nellIdentity Inspector, assegnamogli la classe DettagliProgrammazioneViewController. Cliccate con il destro su Files owner per aprire il popup delle info e collegate opportunamente gli outlet ai controlli textfield e segmentedControl e la action al bottone registra; collegate inoltre la property view alloggetto view. Prima di scrivere la logica di questo controller, inseriamolo nel flusso di gestione degli eventi per verificarne il corretto funzionamento. Torniamo nel metodo ProgrammazioniTableViewController e andiamo nel metodo (void)tableView:(UITableView *)tableView commitEditingStyle: (UITableViewCellEditingStyle)editingStyle forRowAtIndexPath: (NSIndexPath *)indexPath dopo listruzione di creazione dellistanza di Programmazione Programmazione *nuovoProgramma=[[Programmazione alloc] init]; dichiariamo e creiamo unistanza del nostro controller di dettaglio associandolo al file xib DettagliProgrammazioneViewController *dController=[[DettagliProgrammazioneViewController alloc] initWithNibName:@"DettagliProgrammazioneView" bundle:nil];

Abbiamo dichiarato la variabile dController che conterr un riferimento ad unistanza di DettagliProgrammazioneViewController. Listanza viene creata con linvocazione di alloc/initWithNibName. Questo metodo associa al controller il file xib. Poi aggiungiamo dController.laProgrammazione=nuovoProgramma; accediamo la property laProgrammazione del controller e le assegnamo listanza che abbiamo creato sopra; in tal modo passiamo un oggetto da unistanza di una classe ad unaltra. dController.ilTelevisore=self.ilTelevisore; alla property ilTelevisore assegnamo la stessa istanza de ilTelevisore del controller stesso. Infine aggiungiamo [self.navigationController pushViewController:dController animated:YES]; il metodo pushViewController, cui passiamo il nostro viewController, ha leffetto di visualizzare la view del nuovo view controller e di mostrare nella navigation bar un bottone di ritorno alla view precedente. Eseguiamo la nostra app e verifichiamo che la navigazione funzioni. Proviamo a scrivere qualcosa nel campo di testo e osserviamo che appena ci posizioniamo su di esso viene mostrata la tastiera con la quale possiamo scrivere il nome del programma; dobbiamo per gestire la sua scomparsa. Per fare questo necessario individuare un oggetto che assuma il ruolo di delegato per gli eventi sul textField e che si occuper di rimuovere la tastiera quando sar premuto il tasto enter. Il nostro delegate sar listanza di DettagliProgrammazioneViewController. Andiamo dunque nel suo header e prima della { scriviamo <UITextFieldDelegate> in questo modo diciamo che le istanza della nostra classe sono in grado di rispondere agli eventi delegati di un UiTextField. Andiamo ora nel

implementation file per scrivere il codice con il quale gestiamo levento che ci interessa. Aggiungiamo il metodo -(BOOL)textFieldShouldReturn:(UITextField *)textField { [textField resignFirstResponder]; return YES; } questo metodo viene invocato dal sistema ogni volta che viene premuto il tasto return e deve restituire un booleano True/False (o Yes/No) per dire se levento deve essere gestito. Listruzione che abbiamo inserito prima del return [textField resignFirstResponder]; linvocazione di un metodo predefinito di UIResponder, ereditato da UITextField , che ha proprio leffetto di rimuovere la tastiera. Approfondimento: studiare la classe UIResponder. Infine diciamo al textField chi il suo delegato, andando in DettagliProgrammazioneView.xib e dopo aver cliccato con il destro sul campo di testo colleghiamo loutlet delegate con il Files owner. Eseguiamo per provare se funziona la dismissione della tastiera. Passiamo a gestire un altro evento e poi inseriremo la nostra logica elaborativa. Vogliamo gestire la risposta alla selezione di una riga, in questo caso si dovr di nuovo aprire la vista di dettaglio con le informazioni della programmazione selezionata. Anche in risposta a questo evento viene invocato un metodo apposito del delegato di UITableView allinterno del quale andremo a scrivere il nostro codice. Il metodo in questione (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath e lo trovate

nel file ProgrammazioniTableViewController.m, vi ricordo che questa classe delegato per la tableView. Sostituite il suo contenuto con if (indexPath.row < [ilTelevisore.elencoProgrammazioni count]) { Programmazione *nuovoProgramma=[ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row]; DettagliProgrammazioneViewController *dController=[[DettagliProgrammazioneViewController alloc] initWithNibName:@"DettagliProgrammazioneView" bundle:nil]; dController.laProgrammazione=nuovoProgramma; [self.navigationController pushViewController:dController animated:YES]; } Osserviamo che tutto il corpo del metodo racchiuso allinterno dellistruzione if (indexPath.row < [ilTelevisore.elencoProgrammazioni count]) { } con questo controllo eseguiremo la navigazione solo per le celle con indice compreso nel limite dellelenco elencoProgrammazioni. Per quanto riguarda il blocco, lo stesso pattern che abbiamo usato nel metodo di edit per navigare, lunica differenza nella prima istruzione, mentre nel metodo di inserimento abbiamo creato una nuova istanza e labbiamo passata al controller qui abbiamo recuperato listanza che nellelenco delle programmazioni occupa la posizione indicata dalla riga selezionata. Eseguiamo il programma e verifichiamo che anche in risposta alla selezione di una cella si apra la pagina dei dettagli. Bene ora che abbiamo costruito la struttura possiamo scrivere la nostra logica applicativa. Cosa vogliamo che succeda? Quando si visualizza la vista di dettaglio deve esse allineata con i

dati della programmazione selezionata, quando si esce le eventuali modifiche devono essere riportate sul modello e quindi sulla tabella con l'elenco delle programmazioni. Iniziamo con lindividuare i due punti del codice dove sono gestiti i due eventi di visualizazione e scomparsa della view. Questi sono il metodo viewWillAppear ed il metodo viewWillDisappear del controller dei dettagli. Questi due metodi sono invocati automaticamente dal sistema in risposta ai due eventi di nostro interesse, e quindi al loro interno noi scriveremo le istruzioni per allineare Modello e Interfaccia. Nel file DettagliProgrammazioneViewController.m aggiungiamo -(void)viewWillAppear:(BOOL)animated { nomeProgramma.text=laProgrammazione.nomeDelProgramma; } il metodo viewWillAppear, come gi detto sopra, eseguito ogni volta che viene visualizzata la view del controller. Quello che abbiamo scritto significa che ogni volta che viene mostrata la view noi assegnamo alla property text del UITextField il valore della property nomeDelProgramma dellistanza referenziata da laProgrammazione. Ricordate che prima di attivare la transizione delle vista con il pushViewController noi abbiamo assegnato un oggetto a laProgrammazione. Eseguite lapplicazione ed osservate il comportamento quando selezionate una riga con i dati gi presenti. Ripercorrete mentalmente il cammino delle informazioni. Passiamo allaggioramento del secondo elemento dellinterfaccia, cio il segmentedControl con il numero del canale. Se osserviamo il nostro Model vediamo che la classe Programmazione non ha un numero ma ha solo il nome del canale e mentre abbiamo un modo per recuperare il nome a partire dal numero (accedendo al canaliMemorizzati di Televisore) non abbiamo nulla per il contrario; potremmo trovare diverse soluzioni ad es. scorrere i

canaliMemorizzati fino a trovare quello con quel nome e recuperare quindi la sua posizione e di conseguenza lindice; oppure potremmo costruirci direttamente un if-else che a seconda del nome del canale restituisce un indice, modo pessimo ed assolutamente da evitare in quanto si scrive il codice sulla base dei dati, con la conseguenza che se un giorno dovessimo cambiare lordine dei canali dovremmo riscrive una parte del codice. Siccome noi non abbiamo bisogno di complicarci la vita e possiamo farlo, semplicemente cambiamo il Model, cio a livello del Model ci gestiamo il numero del canale invece che il suo nome. Nel file Programmazione.h cambiamo la classe di canaleDaRegistrare da NSString a NSNumber, questa classe labbiamo gi usata altre volte sia direttamente che con la sua sottoclasse NSDecimalNumber. Allieniamo adesso a questa modifica il codice gi scritto. Nel metodo viewDidLoad della classe ProgrammazioniTableViewController cambiamo la parte di inizializzazione con dati di prova come segue Programmazione *programma=[[Programmazione alloc] init]; programma.nomeDelProgramma=@"Film 1"; programma.canaleDaRegistrare=[NSNumber numberWithInt:0]; [self.ilTelevisore.elencoProgrammazioni addObject:programma]; programma=[[Programmazione alloc] init]; programma.nomeDelProgramma=@"Documentario 1"; programma.canaleDaRegistrare=[NSNumber numberWithInt:1]; [self.ilTelevisore.elencoProgrammazioni addObject:programma]; programma=[[Programmazione alloc] init]; programma.nomeDelProgramma=@"Film 2"; programma.canaleDaRegistrare=[NSNumber numberWithInt:2]; in pratica abbiamo cambiato gli assegnamenti alla property canaleDaRegistrare in maniera coerente al nuovo tipo di dato. A questo punto andiamo ad allineare al nuovo modello il metodo

di creazione delle celle - (UITableViewCell *)tableView: (UITableView *)tableView cellForRowAtIndexPath: (NSIndexPath *)indexPath nel quale le istruzioni di costruzione della cella diventano if (indexPath.row<[self.ilTelevisore.elencoProgrammazioni count]) { cell.textLabel.text=((Programmazione *) [self.ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row]).nomeDelProgramma; NSNumber *numCanale=((Programmazione *) [self.ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.row]).canaleDaRegistrare; cell.detailTextLabel.text=[self.ilTelevisore.canaliMemorizzati objectAtIndex:[numCanale integerValue]]; in sostanza prima del riempimento della label di dettaglio creiamo unistanza di NSNumber valorizzata con il contenuto del canaleDaRegistrare ( la stessa espressione della versione precedente per questa volta restituisce un NSNumber con lindice del canale). Per recuperare la NSString da assegnare alla label di dettaglio, prima recuperiamo i valore intero dellindice del canale [numCanale intValue] e con esso accediamo alla posizione dellelenco canaliMemorizzati per recuperarne il nome. Eseguiamo lapp e vediamo leffetto andando subito sul tab delle Programmazioni. Dovreste vedere una tabella nella quale le righe di dettaglio sono vuote. Perch? prendetevi qualche minuto di analisi prima di proseguire e provate a capire il motivo. Suggerimento: ripercorrete il ciclo di vita dellelenco canaliMemorizzati.

... Se osservate la classe Televisore vedrete che l'elenco canaliMemorizzati viene inizializzato nel metodo accendi, quindi per il corretto funzionamento dellapp necessario accendere il televisore prima di andare nel tab Programmazioni. Eseguite di nuovo lapp e verificate che accendendo il televisore e poi andando nelle Programmazioni la visualizzazione sia corretta. Facciamo unaltra prova per evidenziare un altro aspetto. Eseguite lapp e andate nel tab Programmazioni. Poich non abbiamo acceso il televisore manca la riga dettagli. Ora andate nel tab Televisore ed accendetelo. Se torniamo nel tab Programmazioni ci aspetteremmo di vedere la tabella completa anche della riga dettagli, invece no. Il motivo che la tabella stata creata la prima volta e non viene aggiornata. Quello che ci serve che ogni volta che viene mostrata questa view la tabella sia aggiornata. Come al solito individuiamo prima il metodo nel quale gestire questo evento, il metodo che viene eseguito ogni volta che una view viene mostrata il viewWillAppear. Quindi andiamo nel metodo viewWillAppeardella classe ProgrammazioniTableViewController e aggiungiamo listruzione [self.tableView reloadData]; con la quale si invoca il metodo prefinito reloadData sulla tabella referenziata dalla property tableView. Leffetto di questo metodo proprio quello di aggiornare la visualizzazione della tabella. Bene abbiamo cambiato il tipo di dato di canaleDaRegistrare ed abbiamo allineato il codice a questa variazione. E' importante anche vedere come si lavora sul nostro codice per apportare delle modifiche mentre stiamo programmando. Torniamo ora al metodo viewWillAppear della classe DettagliProgrammazioneViewController nel quale aggiorniamo la visualizzazione dei dettagli della programmazione.

Aggiungiamo numeroCanale.selectedSegmentIndex=[laProgrammazione.canale DaRegistrare intValue]; E unistruzione che abbiamo gi incontrato. Stiamo assegnando alla property selectedSegmentIndex , che indica il pulsante selezionato del segmentedcontrol, il valore intero rappresentato dallistanza di NSNumber referenziata da canaleDaRegistrare. Eseguiamo ancora una volta il programma e verifichiamo che tutto funzioni correttamente. Passiamo ora a gestire laggiornamento delle informazioni una volta che si esce dalla view dei dettagli. Il metodo che gestisce questo evento viewWillDisappear, quindi in questo metodo scriveremo le istruzioni per aggiornare le property della programmazione con i valori presenti sullinterfaccia. Nel file DettagliProgrammazioneViewController aggiungete il metodo -(void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; if (nomeProgramma.text==nil) { laProgrammazione.nomeDelProgramma=@""; } else { laProgrammazione.nomeDelProgramma=[NSString stringWithString:nomeProgramma.text]; } laProgrammazione.canaleDaRegistrare=[NSNumber numberWithInt:numeroCanale.selectedSegmentIndex]; } La prima istruzione [super viewWillDisappear:animated]; linvocazione dello stesso metodo nella superclasse, buona regola di programmazione inserire questa invocazione anche se pu non avere effetti specifici.

Poi controlliamo se abbiamo inserito un nome nel campo del programma e, se non lo abbiamo fatto gli assegnamo la stringa vuota (@) altrimenti con laProgrammazione.nomeDelProgramma=[NSString stringWithString:nomeProgramma.text]; assegnamo alla property nomeDelProgramma il contenuto del campo di testo nomeProgramma. Il valore che assegnamo un istanza di NSString creata a partire dal contenuto del campo. Infine con laProgrammazione.canaleDaRegistrare=[NSNumber numberWithInt:numeroCanale.selectedSegmentIndex]; assegnamo alla property canaleDaRegistrare lindice del tasto selezionato nel segmentedcontrol. In maniera coerente con il tipo di dato il valore intero dellindice viene passato ad un costruttore di istanza per NSNumber ed il risultato assegnato alla property. Eseguiamo il programma e proviamo a modificare i valori di dettaglio verificando leffetto delle modifiche. 21 Aggiungiamo le registrazioni
iOS, Mac, OSX, SDK, Tech Room

Riprendiamo lo sviluppo della nostra funzione di registrazione. Osserviamo che il bottone per avviare le registrazioni dovrebbe apparire solo quando apriamo la view da una programmazione e non quando ci arriviamo creandone una nuova. Per fare questo, agiremo sulla propriet del bottone che lo rende visibile o no. Quindi quello che dobbiamo fare programmare qualcosa del tipo

se sto mostrando i dettagli di una programmazione esistente visualizza il bottone registra altrimenti nascondo il bottone registra La prima cosa che dobbiamo decidere dove implementare questa sequenza; ovviamente il punto pi corretto il momento di visualizzazione della view, quindi nel metodo viewWillAppear. Ora dobbiamo decidere come implementare la condizione sto mostrando i dettagli di una programmazione esistente , possiamo riflettere sul fatto che se siamo nel caso di una nuova programmazione le sue propriet non sono ancora valorizzate, quindi possiamo verificare questo aspetto su una delle propriet della programmazione per distinguere in quale caso ci troviamo. Per capire meglio quello che sto dicendo fate questa prova: inserite nel metodo viewWillAppear di DettagliProgrammazioneViewController listruzione NSLog(@"-----%@%@",laProgrammazione.nomeDelProgramma,laProgrammazione. canaleDaRegistrare); listruzione NSLog labbiamo vista fin troppe volte quindi non ve la illustro. Provate ad eseguire il programma ed osservate i messaggi a console quando visualizzate i dettagli di una programmazione gi presente o quando ne create una nuova. Sostituiamo quindi listruzione NSLog che abbiamo appena inserito con if (laProgrammazione.nomeDelProgramma==nil) { } else { } con la quale andiamo a verificare se il nomeDelProgramma contiene qualcosa oppure no per discriminare tra i due percorsi di attivazione della view. Una volta che abbiamo capito come implementare la condizione

passiamo ad analizzare le azioni nei due blocchi. In entrambe i casi dobbiamo dire qualcosa al bottone, abbiamo quindi bisogno di un canale dal controller verso il bottone, cio abbiamo bisogno di un Outlet. Nel file DettagliProgrammazioneViewController.h aggiungiamo alle variabili di istanza della classe IBOutlet UIButton *avviaRegistrazione; e nel file DettagliProgrammazioneView.xib colleghiamo loutlet al bottone cliccando con il destro su Files owner e trascinando il mouse dalloutlet al bottone. A questo punto possiamo tornare nel metodo viewWillAppear di DettagliProgrammazioneViewController per implementare la logica per mostrare e nascondere il bottone. Nel ramo True dellif che abbiamo inserito sopra stiamo gestendo la condizione di nomeDelProgramma==nil, cio siamo nella situazione di una nuova programmazione e vogliamo nascondere il bottone; quindi allinterno di questo blocco scriviamo avviaRegistrazione.hidden=TRUE; cio assegnamo True alla property hidden del bottone referenziato dal nostro outlet. leffetto di questo assegnamento quello di nascondere il bottone nellinterfaccia. In maniera analoga nel ramo False, e quindi nel blocco dopo else, scriviamo avviaRegistrazione.hidden=FALSE; che ha leffetto contrario. Ora siamo pronti per implementare la logica della registrazione. Come vi dicevo noi non registreremo nulla ma lunica cosa che faremo sar quella di creare oggetti di Registrazione ed associarli alla corrispondente programmazione. Andiamo quindi nel metodo registra, del file

DettagliProgrammazioneViewController.m, che vi ricordo essere quello associato come action al bottone registra dellinterfaccia. La prima cosa che dobbiamo fare creare unistanza della classe Registrazione, abbiamo quindi bisogno della import della classe nel file DettagliProgrammazioneViewController.h. Nel metodo registra scriviamo Registrazione *nuovaRegistrazione=[[Registrazione alloc] init]; per creare una nuova istanza di Registrazione. Ora scriviamo nuovaRegistrazione.dataOraRegistrazione=[NSDate date]; in questo modo assegnamo alla property dataOraRegistrazione unistanza di NSDate che rappresenta la data e lora attuali. Infine con [self.laProgrammazione addRegistrazioniAssociateObject:nuovaRegistrazione]; aggiungiamo listanza di registrazione che abbiamo appena creato alla programmazione selezionata. Eseguite lapplicazione e verificate che non ci siano errori. Finora, per quanto riguarda Registrazione, abbiamo sostanzialmente lavorato a livello Model, cio abbiamo creato istanze e le abbiamo relazionate con altri oggetti. Ora dobbiamo costruire il livello View per vedere gli elenchi di registrazioni delle diverse programmazioni. Scegliamo di inserire un altro tab che mostra una view come quella in figura

La struttura della view si ottiene con una tableview di tipo grouped nella quale avremo una sezione per ogni programmazione allinterno della quale elencheremo le diverse registrazioni. Creiamo il file con linterfaccia aggiungendo al progetto un file XIB di tipo View e lo chiamiamo ElencoRegistrazioni. Cancelliamo loggetto view dal gruppo Objects ed aggiungiamo una TableView prendendola dalla Library nel frame di destra. Ora selezioniamo la tableview e nellAttributes Inspector cambiamo lo style da Plain a Grouped. Apriamo il file MainWindow.xib, esplodiamo la gerarchia del Tab Bar Controller nella sezione Objects del frame di sinistra ed aggiungiamo un TableViewController scelto dalla Library. Selezioniamo il Tab Bar Item allinterno del Table View Controller appena inserito e nellAttributes Inspector cambiamo il Title scrivendoci Registrazioni. Apriamo la gerarchia del Table View Controller e cancelliamo loggetto view al suo interno. Ora selezioniamo il Table View Controller e nellAttributes Inspector scriviamo ElencoRegistrazioni nel campo NIB Name.

Ora andiamo a creare la classe del controller. Clicchiamo con il bottone destro sulla cartella myTv in XCode; scegliamo Add File e dal gruppo Cocoa Touch selezioniamo UIViewController subclass. Gli diamo il nome ElencoRegistrazioniController e lo definiamo sottoclasse di UITableViewController. Prima di definire la nostra classe Controller assegnamola agli oggetti di interfaccia. Selezioniamo il file MainWindow.xib, selezioniamo il Table View Controller delle registrazioni assegnamogli la classe ElencoRegistrazioniController nel campo Class. Ora apriamo il file ElencoRegistrazioni.xib, selezioniamo il Files owner e nellIdentity Inspector assegnamogli la classe ElencoRegistrazioniController. Clicchiamo con il bottone destro sul Files owner e leghiamo loutlet view alloggetto tableview. Clicchiamo con il destro sulla tableview e colleghiamo il datasource ed il delegate al Files owner. Eseguiamo lapplicazione per verificare che non ci siano errori. Riprendiamo il nostro controller per scrivere la logica di collegamento tra Model e View. La prima considerazione che il nostro controller dovr accedere allelenco delle programmazioni detenuto dal televisore; quindi abbiamo bisogno di una property di Televisore che si riferir al televisore, quindi in ElencoRegistrazioniController.h aggiungiamo @property (nonatomic,retain) Televisore *ilTelevisore; ovviamente dobbiamo fare la import della classe Televisore e la synthesize nellimplementation file. Andiamo ad implementare i metodi di datasource per il popolamento della tableview. La prima cosa da fare dire al sistema da quante sezioni composta la nostra tabella, questo si fa implementando il metodo numberOfSectionsInTableView del datasource per fargli restituire il numero di sezioni. Scorriamo il file ElencoRegistrazioniController.m fino a trovare il metodo.

Cancelliamo, se presente (dipende dalla versione di XCode), la riga #warning Potentially incomplete method implementation. Attualmente questo metodo restituisce 0. Qual il numero di sezioni che dobbiamo avere nella nostra tabella? Se abbiamo una sezione per ogni programmazione, allora sar pari al numero di programmazioni; quindi sostituiamo return 0; con return [ilTelevisore.elencoProgrammazioni count]; Cio accediamo alla property elencoProgrammazioni di ilTelevisore, che ci restituisce un NSMutableArray con le programmaziuoni, ed invochiamo il metodo count che un metodo predefinito per NSArray che restituisce il numero di elementi. La seconda cosa dire al sistema qual il titolo di ogni sezione, questo si fa implementando il metodo -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { } che prende in input il numero della sezione e restituisce un NSString con il titolo per quella sezione. Per noi il titolo il nome del programma cio il valore della property nomeDelProgramma della programmazione contenuta nella posizione section dellelenco elencoProgrammazioni di ilTelevisore. Quindi aggiungiamo il metodo -(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { return ((Programmazione *)[ilTelevisore.elencoProgrammazioni objectAtIndex:section]).nomeDelProgramma; } abbiamo fatto il type casting a (Programmazione *) dellelemento

restituito dallaccesso allarray per poter accedere alla sua property nomeDelProgramma; per far questo necessario fare limport di Programmazione. Ora dobbiamo dire al sistema quante righe ha ogni sezione, per fare questo necessario implementare il metodo - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { } che riceve in ingresso il numero della sezione e restituisce il numero di righe per quella sezione. Se le nostre sezioni devono contenere tante righe quante sono le registrazioni, allora il valore da restituire sar il numero di elementi dellarray registrazioniAssociate della programmazione che si trova nella posizione section dellarray elencoProgrammazioni di ilTelevisore. Quindi scorriamo il file fino a trovare il metodo e cancelliamo, se presente (dipende dalla versione di XCode), la riga #warning Potentially incomplete method implementation. Attualmente questo metodo restituisce 0, quindi sostituiamo return 0; con Programmazione *tmpProgrammazione=((Programmazione *) [ilTelevisore.elencoProgrammazioni objectAtIndex:section]); return [tmpProgrammazione.registrazioniAssociate count]; Per comodit abbiamo spezzato listruzione in due; nella prima recuperiamo loggetto nella posizione section dallelenco delle programmazioni e poi restituiamo il numero di registrazioni associate. Passiamo al metodo con il quale costruire le celle per la tabella. Nel metodo

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath prima di return cell; aggiungiamo Programmazione *tmpProgrammazione=(Programmazione *) [ilTelevisore.elencoProgrammazioni objectAtIndex:indexPath.section]; Registrazione *tmpRegistrazione=(Registrazione *) [tmpProgrammazione.registrazioniAssociate objectAtIndex:indexPath.row]; cell.textLabel.text=[tmpRegistrazione.dataOraRegistrazione description]; Vi lascio come esercizio lanalisi di queste istruzioni. Infine nel metodo viewWillAppear aggiungiamo listruzione per ricaricare la tabella ogni volta che viene visualizzata [self.tableView reloadData]; La cosa che ci rimane da fare assegnare listanza di Televisore alla property, seguiamola stessa logica adottata per gli altri controller. Nel file myTvAppDelegate.h aggiungiamo #import "ElencoRegistrazioniController.h" e @property (nonatomic, retain) IBOutlet ElencoRegistrazioniController *myRegistrazioniController; abbiamo dichiarato una property quindi necessaria la synthesize. Nel file myTvAppDelegate..m andiamo nel metodo didFinishLaunchingWithOptions e aggiungiamo myRegistrazioniController.ilTelevisore=tv1; Infine andiamo ad agganciare loutlet che abbiamo appena creato. Apriamo il file MainWindow.xib, clicchiamo con il destro sulloggetto My Tv App Delegate e colleghiamo loutlet

myRegistrazioniController con il Table View Controller Registrazioni. Eseguite lapplicazione e verificate il corretto funzionamento.

22 Pre-conclusione
iOS, Mac, OSX, SDK, Tech Room

Con il capitolo 21 abbiamo concluso il percorso introduttivo alle basi del linguaggio Objective-C e di Cocoa. Abbiamo visto le istruzioni fondamentali del linguaggio (la sequenza, listruzione condizionale, il ciclo, linvocazione di messaggi). Lelemento base del linguaggio loggetto che unistanza di una classe che ne descrive le sue caratteristiche in termini di propriet e metodi. Un programma in Objective-C costituito da un insieme di oggetti che cooperano nella realizzazione della logica applicativa tramite lo scambio di messaggi che si traducono nellinvocazione di metodi delloggetto ricevente. Le classi sono strutturabili in gerarchie allinterno delle quali esiste il meccanismo di ereditariet. Inoltre le classi (o meglio gli oggetti) possono relazionarsi con il meccanismo di composizione, cio un oggetto costituito da altri oggetti, un televisore ha al suo interno un elenco di programmazioni e lelenco un oggetto e le programmazioni sono altri oggetti.

Abbiamo visto che lo sviluppo di un programma deve seguire determinati pattern; il pi importante (in quanto costituisce la struttura di base del codice) sicuramente il pattern MVC che separa gli oggetti che rappresentano le informazioni (Model) dagli oggetti destinati ad interagire con lUtente (View) collegandoli attraverso un livello di oggetti nei quali viene implementata la logica elaborativa (Control). Abbiamo poi usato diverse classi (NSString, NSArray, NSNumber) predefinite in Cocoa che esattamente questo: un insieme (enorme) di classi predefinite, raggruppate in framework, da utilizzare nelle nostre applicazioni. In realt esistono due framework leggermente diversi Cocoa per lo sviluppo di app per Mac OSX e Cocoa Touch per lo sviluppo di app per iOS, ma limportante capire il concetto di framework. Di seguito vi riporto alcuni dei framework di Cocoa Touch Audio and Video Core Audio OpenAL Media Library AV Foundation Data Management Core Data SQLite Graphics and Animation Core Animation OpenGL ES Quartz 2D Networking and Internet Bonjour WebKit

BSD Sockets User Applications Address Book Core Location Map Kit Store Kit Come potete immaginare impossibile conoscere tutte le classi (propriet e metodi) di tutti questi framework, per cui la cosa pi importante da imparare la consultazione della documentazione allegata ad XCode. Lultimo sviluppo che faremo sar proprio lintroduzione di un altro di questi framework: Core Data. Come potete vedere dallelenco un framework per la gestione dei dati che useremo per introdurre la persistenza nella nostra applicazione.

23 Persistenza
iOS, Mac, OSX, SDK, Tech Room

Se lanciamo la nostra applicazione, creiamo alcune programmazioni e registrazioni poi la chiudiamo (non mettendola in background) e la lanciamo nuovamente, osserviamo che le programmazioni e registrazioni create precedentemente non ci sono pi. Introdurre la persistenza in unapplicazione serve proprio a superare questo problema. Possiamo dire che la persistenza dei dati la capacit di unapplicazione di utilizzare, allinterno di una sua esecuzione, dati creati in esecuzioni precedenti.

Esistono diversi meccanismi per implementare la persistenza e tutti si fondano sulla creazione di file. Si possono usare direttamente i file piatti, con le operazioni tipiche di lettura e scrittura (rif. NSFileManager); si possono usare file strutturati in XML, utilizzando un parser per il recupero delle informazioni (rif. property list e NSUserDefaults); si possono usare dei database che sono un modo per strutturare dei dati allinterno di uno o pi file (rif. SQLite). In Cocoa disponibile il framework Core Data che astrae un database SQLite per modellare le classi della nostra applicazione, in tal modo noi continueremo a programmare (con piccolissime variazioni) come siamo abituati e CoreData si preoccuper di rendere persistenti i nostri dati. Il framework Core Data molto articolato e anche molto ben documentato per cui assolutamente necessario che approfondiate tutti i concetti che vedremo nella documentazione Apple. Nella progettazione di unapplicazione si definisce prima dello sviluppo quali sono le classi persistenti, cio quelle i cui oggetti vogliamo disponibili tra diverse esecuzioni dellapplicazione. Noi per partiamo da unapplicazione che abbiamo gi sviluppato quindi procederemo con un percorso che ci porter a sostituire alcune classi con altre persistenti. Come prima cosa diciamo al sistema che useremo Core Data, dobbiamo quindi aggiungere il framework al progetto. Selezionate licona del progetto, cio la radice dellalbero in XCode; ora selezionate il target e nel tab Build Phases aprite la sezione Link Binary with Libraries. Cliccate sul + e nel pannello che vi viene presentato scegliete Core Data ed aggiungetelo. Facendo click sulla cartella del progetto selezioniamo New File e scegliamo Data Model nel gruppo Core Data dei template iOS, chiamiamolo myTv e salviamo. Al progetto viene aggiunto un file myTv.xcdatamodeld, selezionatelo e, se necessario, scegliamo la visualizzazione del diagramma grafico (una view bianca a quadretti).

Cliccate su Add Entity e appare unentit nella view del diagramma. Fate doppio click sul nome Entity e scrivete ProgrammazioneCD. Con lentit selezionata, cliccate su Add Attribute, nel Data Model Inspector (aprite il pannello a destra) cambiate il nome dellattributo in canaleDaRegistrare e scegliete il tipo Integer 16. Aggiungete un altro attributo chiamato nomeDelProgramma di tipo String. Non aggiungete lattributo con lelenco delle Registrazioni. Ora nella view del diagramma aggiungete unaltra entity chiamandola RegistrazioneCD. Aggiungete a questa entity un attributo, chiamato dataOraRegistrazione di tipo Date. Dopo aver selezionato lentit ProgrammazioneCD aggiungete ora una relazione, nelle ultime versioni di XCode, mantenendo premuto il mouse su Add Attribute si evidenziano altre operazioni tra cui Add Relationship che dovete scegliere. Nel Data Model Inspector della relazione cambiate il nome in registrazioniAssociate, nel campo Destination scegliete RegistrazioneCD e attivate il flag To-Many Relationship, in questo modo stiamo dicendo che aduna programmazione potranno essere associate pi registrazioni. Nel campo Delete Rule selezionate Cascade, in questo modo diciamo che quando cancelliamo una programmazione dovranno essere cancellate anche tutte le sue registrazioni. Ora selezionate lentity RegistrazioneCD ed aggiungete una relazione chiamata programmazioneAssociata. Nel Data Model Inspector scegliete come Destination ProgrammazioneCD e nel campo Inverse scegliete la relazione registrazioniAssociate; verificate che il flag To-Many Relationship non sia attivato, una registrazione relativa una sola programmazione.

Bene, abbiamo finito di definire il nostro modello dei dati persistente. Vediamo ora come possiamo utilizzarlo allinterno del nostro codice. Per poter utilizzare queste entit abbiamo bisogno diclassi; selezioniamo le due entity e clicchiamo sul menu File>New File, nel pannello che si presenta scegliete NSManagedObject subclass dal gruppo Core Data della sezione iOS. Vengo automaticamente creati i file per due classi con lo stesso nome delle entity e se tornate sul modello dati e selezionate una entity, nel Data Model Inspector troverete che il campo Class stato valorizzato con il nome della classe opportuna. Passiamo a sviluppare il nuovo codice. Iniziamo con la riflessione che CoreData richiede la creazione di tre istanze standard che costituiscono quella che viene chiamata Core Data Stack; come ho anticipato necessario che approfondiate questi concetti nella documentazione Apple. Nel file myTvAppDelegate.h aggiungete le tre property @property (nonatomic, retain, readonly) NSManagedObjectContext *managedObjectContext;

@property (nonatomic, retain, readonly) NSManagedObjectModel *managedObjectModel; @property (nonatomic, retain, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; per queste tre property inseriamo nel file myTvAppDelegate.m @synthesize managedObjectContext = __managedObjectContext; @synthesize managedObjectModel = __managedObjectModel; @synthesize persistentStoreCoordinator = __persistentStoreCoordinator; Con questa sintassi per le synthesize indichiamo anche il nome della variabile di istanza che viene implicitamente creata ed associata alla property; non necessario, in quanto se non lo facessimo avremmo una variabile di istanza con lo stesso nome della property, ma proprio per questo motivo utile per poter distinguere quando si parla delluna e quando dellaltra. Andiamo ora a creare i nostri metodi getter ed aggiungiamo - (NSPersistentStoreCoordinator *)persistentStoreCoordinator { if (__persistentStoreCoordinator != nil) { return __persistentStoreCoordinator; } NSURL *documentsDirectory=[[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject]; NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:@"myTv.sqlite"]; NSError *error = nil; __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]]; if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType

configuration:nil URL:storeURL options:nil error:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } return __persistentStoreCoordinator; } Questo metodo crea il primo oggetto del Core Data Stack. La prima cosa che fa chiedersi se la variabile di istanza associata ha gi un valore nel qual caso lo restituisce. Altrimenti si crea un riferimento di tipo NSURL ad un file chiamato myTv.sqlite che il database SQLite che Core Data user per gestire le nostre informazioni. Il file sar posizionato allinterno della documentsDirectory dellapplicazione. Con linvocazione [__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:nil error:&error] il file sar utilizzato per gestire il nostro modello dati. Passiamo al secondo getter aggiungendo - (NSManagedObjectModel *)managedObjectModel { if (__managedObjectModel != nil) { return __managedObjectModel; } NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"myTv" withExtension:@"momd"]; __managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; return __managedObjectModel; } Questo getter in sostanza restituisce il riferimento al nostro modello dei dati che abbiamo disegnato nel file

myTv.xcdtamodeld. Infine, aggiungiamo il terzo getter - (NSManagedObjectContext *)managedObjectContext { if (__managedObjectContext != nil) { return __managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { __managedObjectContext = [[NSManagedObjectContext alloc] init]; [__managedObjectContext setPersistentStoreCoordinator:coordinator]; } return __managedObjectContext; } La struttura di questi metodi standard per cui si possono riutilizzare ogni volta che c bisogno di lavorare con Core Data facendo solo attenzione ad aggiornare ogni volta il nome del file SQLite nel getter del persistentStoreCoordinator e del modello dati in quello di managedObjectModel. Inoltre tenete presente che se quando create il vostro progetto attivate il flag CoreData questi metodi saranno creati automaticamente nel template. Iniziamo ora a scrivere la parte di codice che ci serve per utilizzare le nuove classi. Prima cosa dobbiamo decidere dove intervenire, il modo a mio avviso pi lineare quello di interevnire nel momento in cui si crea un oggetto Televisore, mentre ora crea un elenco di programmazioni vuoto, noi faremo in modo che vengano lette le programmazioni archiviate e assegnate allelenco. Dichiariamo quindi, in Televisore.h, il metodo

-(void)leggiProgrammazioni; Andiamo ora nellimplementation file a definirlo. -(void)leggiProgrammazioni { } Abbiamo ora bisogno di indicare di quale entit vogliamo recuperare le istanze, questo si ottiene creando unistanza di NSEntityDescription -(void)leggiProgrammazioni { NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"ProgrammazioneCD" inManagedObjectContext:managedObjectContext]; } come vedete per creare questa istanza abbiamo bisogno di un riferimento al managedObjectContext; per cui andiamo in Televisore.h e creiamo una property adeguata @property(nonatomic,retain) NSManagedObjectContext *managedObjectContext; in Televisore.m facciamo la synthesize. In seguito vedremo come valorizzarla al momento della creazione dellistanza di Televisore. Ora torniamo al metodo leggiProgrammazioni ed associamo lentity alla fetchRequest -(void)leggiProgrammazioni { NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"ProgrammazioneCD" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; } Possiamo decidere di ordinare le nostre programmazioni in base al numero del canale da registrare creando un NSSortDescriptor -(void)leggiProgrammazioni {

NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"ProgrammazioneCD" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"canaleDaRegistrare" ascending:YES]; } e, poich potremmo avere pi criteri di ordinamento creiamo un array con tutti i sortDescriptors -(void)leggiProgrammazioni { NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"ProgrammazioneCD" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"canaleDaRegistrare" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; } e lo associamo alla fetchRequest -(void)leggiProgrammazioni { NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"ProgrammazioneCD" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"canaleDaRegistrare" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; }

a questo punto possiamo eseguire la request e assegnarne il risultato a elencoProgrammazioni, questo si ottiene con linvocazione del metodo executeFetchRequest -(void)leggiProgrammazioni { NSError *error; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:@"ProgrammazioneCD" inManagedObjectContext:managedObjectContext]; [fetchRequest setEntity:entity]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"canaleDaRegistrare" ascending:YES]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; self.elencoProgrammazioni=[NSMutableArray arrayWithArray: [managedObjectContext executeFetchRequest:fetchRequest error:&error]]; } come vedete il metodo executeFetchRequest richiede un parametro (error:) che sia un riferimento ad unistanza di NSError che ho dichiarato in testa al metodo. Il risultato di executeFetchRequest un NSArray ma poich noi abbiamo bisogno di un NSMutableArray per la nostra property elencoProgrammazioni, ci costruiamo unistanza di questa classe a partire dal risultato ottenuto. A questo punto nel metodo init cancelliamo self.elencoProgrammazioni=[[NSMutableArray alloc] init]; Dal punto di vista della struttura applicativa rimasto aperto il punto relativo alla valorizzazione della property managedObjectContext. Chiediamoci chi conosce listanza valorizzata di managedObjectContext; la risposta lappDelegate nel quale abbiamo implementato lo stack CoreData. Ma

appDelegate conosce anche listanza di Televisore in quanto lui che la crea, la cosa quindi semplice: nel metodo didFinishLaunchingWithOptions di myTvAppDelegate dopo listruzione Televisore *tv1=[[Televisore alloc] init]; con la quale creiamo listanza di Televisore aggiungiamo tv1.managedObjectContext=self.managedObjectContext; con la quale assegnamo alla property di tv1 il managedObjectContext dello stack CoreData; e aggiungiamo anche [tv1 leggiProgrammazioni]; con la quale invochiamo il metodo che abbiamo appena scritto per popolare elencoProgrammazioni. Passiamo al file ProgrammazioniTableViewController che quello nel quale stiamo gestendo le Programmazioni e che invece ora dovr gestire ProgrammazioneCD. Prima cosa aggiungiamo la import di ProgrammazioneCD. #import "ProgrammazioneCD.h" Nel metodo viewDidLoad cancelliamo le istruzioni che usavamo per creare tre programmazioni iniziali. Programmazione *programma=[[Programmazione alloc] init]; programma.nomeDelProgramma=@"Film 1"; programma.canaleDaRegistrare=[[NSNumber numberWithInt:0] retain]; [self.ilTelevisore.elencoProgrammazioni addObject:programma]; programma=[[Programmazione alloc] init]; programma.nomeDelProgramma=@"Documentario 1"; programma.canaleDaRegistrare=[NSNumber numberWithInt:1]; [self.ilTelevisore.elencoProgrammazioni addObject:programma]; programma=[[Programmazione alloc] init]; programma.nomeDelProgramma=@"Film 2";

programma.canaleDaRegistrare=[NSNumber numberWithInt:2]; [self.ilTelevisore.elencoProgrammazioni addObject:programma]; Nel metodo cellForRowAtIndexPath nel quale creiamo le celle della tableview delle programmazioni sostituiamo Programmazione con ProgrammazioneCD. Passiamo ora al metodo commitEditingStyle nel quale viene creato un nuovo oggetto per al nostra programmazione e sostituiamo Programmazione *nuovoProgramma=[[Programmazione alloc] init]; con ProgrammazioneCD *nuovoProgramma=[NSEntityDescription insertNewObjectForEntityForName:@"ProgrammazioneCD" inManagedObjectContext:managedObjectContext]; Con questa istruzione creiamo una nuova istanza persistente della classe ProgrammazioneCD. Come potete osservare XCode vi segnala un errore perch stiamo usando managedObjectContext e non lo abbiamo dichiarato. Andiamo quindi nel file header e dichiariamo la property @property (nonatomic, retain) NSManagedObjectContext *managedObjectContext; e nel file implementation inseriamo la sua synthesize. In seguito vedremo come valorizzarla correttamente. Ora andiamo in Televisore.h ed osserviamo che il metodo inserisciProgrammazione prende un argomento di tipo Programmazione, mentre ora noi gli stiamo passando unistanza di ProgrammazioneCD, cambiamo quindi la sua intestazione sia nel header che nellimplementation. Ricordiamo, come sempre, di fare la import della classe per poterla usare. Continuiamo con lallineamento alla nuova classe andando nel file DettagliProgrammazioneViewController.h e osserviamo che c una property laProgrammazione che si riferisce alla classe

Programmazione, anche qui cambiamo la classe in ProgrammazioneCD. Torniamo al file ProgrammazioniTableViewController.m (non fatevi prendere dal mal di mare) e andiamo nel metodo didSelectRowAtIndexPath al cui interno stiamo creando una variabile (nuovoProgramma) che si riferisce a Programmazione, modifichiamo anche qui la classe. Bene fermiamoci un attimo perch dovremmo aver finito di aggiornare la parte di creazione di una nuova programmazione, prima di eseguire il programma per vedere se questa parte funziona ricordiamo che dobbiamo valorizzare la property managedObjectContext del nostro oggetto istanza di ProgrammazioniTableViewController. Qual il nostro oggetto? E quello referenziato dal IBOutlet myProgController di myTvAppDelegate. Quindi andiamo in myTvAppDelegate.m e, nel metodo didFinishLaunchingWithOptions, dopo listruzione myProgController.ilTelevisore=tv1; aggiungiamo myProgController.managedObjectContext=self.managedObjectCo ntext; Ora eseguite il programma e provate a creare nuove programmazioni. Verificate che vengano correttamente create, poi uscite e lanciate nuovamente lapp, andate nel tab delle programmazioni ecosa trovate? Nulla. E la persistenza dov? Per completare limplementazione della persistenza necessario salvare le modifiche che sono state fatte. Il punto corretto per il salvataggio dipende dalla logica dellapplicazione che state scrivendo; nel nostro caso salveremo il momento in cui chiudiamo lapp. Nel file myTvAppDelegate.m il metodo applicationDidEnterBackground: viene eseguito quando lapplicazione va in background (ad es. premendo il tasto home);

al suo interno iniziamo a scrivere - (void)applicationDidEnterBackground:(UIApplication *)application { if ([self.managedObjectContext hasChanges]) { } } cio iniziamo con il chiederci se ci sono state modifiche ai dati, cio se sono stati creati, cancellati o modificati oggetti delle classi persistenti. Aggiungiamo - (void)applicationDidEnterBackground:(UIApplication *)application { NSError *error = nil; if ([self.managedObjectContext hasChanges]) if (![self.managedObjectContext save:&error]) { NSLog(@"Unresolved error %@, %@", error, [error userInfo]); abort(); } } Cio nel caso di cambiamenti invochiamo il metodo save: di managedObjectContext, che ha proprio lo scopo di salvare le modifiche. Se linvocazione non va a buon fine viene stampato un messaggio di errore. Bene, ora eseguite nuovamente lapp creando delle programmazioni, poi uscite e lanciatela di nuovo, a questo punto andate a verificare se le programmazioni sono ancora presenti. Passiamo a gestire la cancellazione di una programmazione. In Televisore.m abbiamo il metodo cancellaProgrammazione: che riceve come argomento la posizione della programmazione da

cancellare e, nella versione attuale, la cancella dallelenco delle programmazioni. Prima della cancellazione da elencoProgrammazioni inseriamo [managedObjectContext deleteObject:[self.elencoProgrammazioni objectAtIndex:posizione]]; cio invochiamo il metodo deleteObject passandogli lelemento di elencoProgrammazioni che occupa la posizione passata come argomento. Provate ad eseguire lapp cancellando qualche programmazione e verificando che alla successiva esecuzione la programmazione non pi presente. Bene, abbiamo completato la gestione delle programmazioni per renderle persistenti con la nuova classe, passiamo a rendere persistenti le registrazioni. Iniziamo con lindividuare il punto nel quale si crea la registrazione. Se ricordate, noi creiamo una nuova registrazione quando clicchiamo sul bottone registra delle view dei dettagli della programmazione. Andiamo quindi in DettagliProgrammazioneViewController.h ed inseriamo la #import "RegistrazioneCD.h" per usare la nuova classe persistente. Ora andiamo nellimplementation file e nel metodo registra cancelliamo Registrazione *nuovaRegistrazione=[[Registrazione alloc] init]; ed inseriamo RegistrazioneCD *nuovaRegistrazione=[NSEntityDescription insertNewObjectForEntityForName:@"RegistrazioneCD" inManagedObjectContext:managedObjectContext]; cio creiamo la nuova registrazione come istanza della classe persistente. Come potete vedere, anche in questo caso abbiamo bisogno del managedObjectContext e quindi dichiariamo la property con la relativa synthesize. Per valorizzare questa property

dobbiamo individuare il punto nel quale creiamo listanza di DettagliProgrammazioneViewController; questo avviene in due momenti, uno quando creiamo una nuova programmazione ed uno quando ne visualizziamo i dettagli di una gi esistente. Ma il bottone registra, e quindi la possibilit di creare una nuova registrazione, c solo nel secondo caso. Questo evento quello corrispondente alla selezione di una riga della tableview delle programmazioni e cio linvocazione del metodo didSelectRowAtIndexPath di ProgrammazioniTableViewController. In questo metodo, dopo dController.laProgrammazione=nuovoProgramma; aggiungiamo dController.managedObjectContext=self.managedObjectContext; Continuiamo con la sostituzione delle classi. Nel file ElencoRegistrazioniController.h aggiungiamo #import "ProgrammazioneCD.h" #import "RegistrazioneCD.h" Nellimplementation file, nei metodi titleForHeaderInSection, numberOfRowsInSection, cellForRowAtIndexPath cambiamo Programmazione in ProgrammazioneCD. Nel metodo cellForRowAtIndexPath la nuova istruzione per la creazione dellistanza di registrazione RegistrazioneCD *tmpRegistrazione=(RegistrazioneCD *) [[tmpProgrammazione.registrazioniAssociate allObjects] objectAtIndex:indexPath.row]; questo perch coreData gestisce le relazioni tra entity con degli NSSet mentre noi nella versione precedete usavamo degli NSArray sui quali si poteva usare il metodo objectAtIndex per accedere allelemento di una determinata posizione. Ora necessario prima invocare il metodo allObjects sul NSSet per ottenere un NSArray corrispondente.

Per finire facciamo un po di pulizia. Cancelliamo i file Programmazione.h/.m e Registrazione.h/.m che erano relativi alle classi non persistenti, cancelliamo tutte le import relative a queste due classi. Eseguite nuovamente il programma e verificate che sia le programmazioni che le registrazioni siano persistenti.

Das könnte Ihnen auch gefallen