Pagine

2012-03-24

Metodi deprecati e nuove funzionalità

Dispiace dover sospendere il supporto al 10.5, ma in Snow Leopard e Lion sono state introdotte in Cocoa molte possibilità aggiuntive.
La più semplice da implementare senza problemi è certamente la nuova modalità di Full Screen nel 10.7: basta semplicemente aprire in Xcode il nib, selezionare la finestra a cui vogliamo aggiungere questa funzionalità e scegliere nel menu drop-down Full Screen la voce Primary Window.
Il risultato sarà che in Lion la finestra potrà andare a schermo pieno, mentre nei precedenti sistemi la cosa non sarà disponibile ma comunque non creerà problemi.

Più difficile invece gestire alcune funzioni che nel 10.6 e 10.7 sono deprecate, mentre nel 10.5 devono essere usate. In questo caso, il procedimento, pur essendo semplice, deve essere ragionato.

Base + Deployment

L'SDK usato come base deve contenere entrambi metodi (quello attuale e quello deprecato), per cui conviene impostarlo all'SDK più recente (nell'esempio corrente può andare anche quello del 10.6, che contiene il metodo che funziona anche sul 10.7. Per far questo, nel navigatore di sinistra di Xcode clicchiamo sulla prima riga (dove c'è il nome del progetto); nella schermata che viene fuori, altro click sul nome del Project; altro click su Build Settings. Nella lista di impostazioni, cerchiamo la voce Base SDK e impostiamo Mac OS X 10.6; poi cerchiamo la voce Mac OS X Deployment Target e impostiamo a Mac OS X 10.5.
Ora ripetiamo le stesse impostazioni dopo aver cliccato a sinistra sul target del nostro software.
Per verificare che tutto sia a posto, andiamo sul tab Summary in alto, da cui dovremmo vedere la conferma che il Deployment è sul 10.5.

Implementazione

Ora la parte più concettuale: dobbiamo trovare una funzionalità introdotta dal 10.6, cioè che sul 10.5 non esisteva. Il caso che andremo ad esaminare è legato all'apertura di un file tramite un NSOpenPanel, dove vogliamo che l'utente possa selezionare solo alcuni tipi di file.
Il problema è che sul 10.5 si usa il metodo runModalForDirectory:file:types:, mentre nel 10.6 e seguenti tale metodo è deprecato (esiste ancora e funziona, ma in seguito potrebbe essere eliminato) e occorre usare il metodo runModal e in precedenza usare altri metodi per definire la directory di partenza ed i tipi permessi.
Il tutto è complicato dal fatto che il metodo setAllowedFileTypes esiste anche nel 10.5, ma non ha alcun effetto! Anzi, chiamandolo si annulla l'eventuale precedente impostazione! Occorre trovare un altro metodo che ci permetta di discriminare.
Io ho scelto setDirectoryURL, introdotto nel 10.6 per impostare la directory di partenza. Per verificare se questo metodo esiste (e allora siamo sul 10.6) userò il metodo comune respondsToSelector.
Inoltre, noi siamo puristi della programmazione per cui non vogliamo per nessun motivo duplicare inutilmente del codice, usando un IF per scegliere una via o l'altra.

Per prima cosa, settiamo il pannello di apertura e costruiamo il vettore che contiene i tipi permessi:
NSOpenPanel *myPanel = [NSOpenPanel openPanel];
[myPanel setCanChooseFiles:true];
[myPanel setAllowsMultipleSelection:YES];
[myPanel setCanChooseDirectories:NO];
[myPanel setTitle:@"titolo"];
[myPanel setMessage:@"spiegazione"];
NSArray *allowedTypes = [NSArray arrayWithObjects:@"png",@"gif"];
[myPanel setAllowedFileTypes:allowedTypes];
Ora la pensata: usiamo il costrutto IF nella forma implicita, dove avviene l'apertura del pannello con uno dei due metodi, a seconda del sistema:
BOOL ok = ( [myPanel respondsToSelector:@selector(setDirectoryURL:)] ) ? 
          ( [myPanel runModal] == NSFileHandlingPanelOKButton ) : 
          ( [myPanel runModalForDirectory:nil file:nil types:allowedTypes] == NSOKButton );
Notiamo che la scelta viene fatta sul risultato del metodo respondsToSelector:.
A questo punto il pannello è ormai chiuso e a noi basta controllare se la variabile ok è vera (cioè l'utente ha fatto una scelta e cliccato su ok:
if ( ok ) {
   <...facciamo qualcosa...>
}

Nota: avremmo anche potuto calcolare la versione del sistema su cui ci troviamo, ma questo implica operazioni non semplici e qualche volta con risultati strani; in questo modo, la sequenza è semplicissima.

2012-03-11

Errore "property cannot pair"

Vi siete mai trovati davanti all'errore sibillino:
error: writable atomic property 'miaProperty' cannot pair a synthesized setter/getter with a user defined setter/getter
Bene: significa che state usando in modo disinvolto la definizione delle property di Cocoa!
Infatti, nella definizione
@property (retain,...) NSObject *miaProperty;
se non viene esplicitato nonatomic, i metodi saranno atomic per default. Allora non potrete usare @synthetize nell'implementazione della classe, fornendo anche una vostra implementazione degli stessi metodi.
Quindi, per eliminare il problema, potete fare una delle 3 cose seguenti:
- usare la direttiva @dynamic (invece di @synthetize);
- usare @synthetize, ma tenersi i metodi automatici, senza fornirne di propri;
- non usare alcuna direttiva e scrivere per proprio conto entrambi i metodi setter/getter.
Esiste anche un quarto metodo, che consiste nel dichiarare nonatomic la property, consigliabile solamente se non abbiamo necessità di settare/leggere la property da thread diversi.

2012-03-04

Aprire un file con l'applicazione di default

La cosa è molto semplice, ma se non si sa dove cercarla si rischia di girare a vuoto nella documentazione. Chi ha esperienza di Applescript, tende a cercare la soluzione nel Finder (il famoso comando tell Application...).
Apple invece ha nascosto la funzionalità in un oggetto tutto-fare, chiamato NSWorkspace, di cui esiste normalmente una sola istanza, ottenuta col metodo sharedWorkspace.

Quindi, come esempio, vediamo come far aprire dall'applicazione di default un file di testo mioFile che si trova sulla scrivania:
NSString *percorso = [NSString stringWithString:@"/Users/utente/Desktop/mioFile.text"];
NSWorkspace *istanza = [NSWorkspace sharedWorkspace];
if ( ! [istanza openFile:percorso] ) NSLog(@"Non ci sono riuscito");
Nella stringa percorso abbiamo immagazzinato il path al file, nome compreso, che diamo al metodo openFile dell'NSWorkspace. Il ritorno del metodo viene usato per controllare che l'apertura sia andata a buon fine.

Bindings via codice: i falsi amici

I Binding sono una benedizione del cielo, non lo si può nascondere! Con pochi click, e senza scrivere una riga di codice (o comunque scrivendone pochissime, se dobbiamo impostare qualche property aggiuntiva) possiamo aggiornare campi e controlli dell'interfaccia grafica, senza andare ad intaccare le performance dell'applicazione.

La magia che sta dietro ai Binding è ancora oscura ai più, per quanto sia largamente spiegata nella Cocoa Bindings Reference. L'importante è che, in qualsiasi condizione, questi sono in grado di semplificarci di molto il lavoro. Ma, purtroppo, la possibilità di abilitarli e configurarli direttamente dall'interfaccia grafica dell'Xcode, preclude molte possibilità ed, in alcune condizioni, non è abbastanza personalizzabile per essere utilizzata.

Per questo, ci viene in aiuto il codice, che ci permette di ottenere lo stesso risultato che si ottiene collegando i Binding dalla GUI di Xcode, tramite un metodo Cocoa. Il problema è che questo metodo non è affatto spiegato bene, e nella documentazione dedicata a lui vengono riportate informazioni al quanto generiche.
- (void)bind:(NSString*)bind toObject:(id)object withKeyPath:(NSString*)keyPath options:(NSDictionary*)options;
A prima vista, non sembrerebbe una cosa complicata: il fatto è che, appena ci si immerge nella compilazione dei parametri del metodo, ci si accorge che il valore previsto dal parametro "bind" è una semplice stringa. La domanda sorge, quindi, spontanea: cosa devo metterci lì?

La documentazione Apple non aiuta per niente. Ma gironzolando per Internet si viene a scoprire che esistono delle costanti da utilizzare per i binding predisposti da Apple: queste costanti sono elencate in questa pagina, dove è anche elencato il metodo proposto più sopra, ma le due cose non vengono collegate. Le costanti si trovano in fondo alla pagina, ma sarete voi a dover capire quali di quelle sono supportate dall'oggetto di cui dovete fare il bind.

Un ulteriore metodo molto interessante, è l'antagonista di quello precedente, ovvero:
- (void)unbind:(NSString*)bind;
Anche qui, il valore della stringa bind va ricercato tra le costanti che vi ho indicato in precedenza e, chiaramente, serve per annullare l'effetto causato dal metodo bind:toObject:withKeyPath:options:, eliminando così il binding