Pagine

2013-05-02

Bindings e model: come aggiornare

I bindings in Cocoa sono una benedizione dal cielo: evitano di scrivere montagne di codice per fare in modo che quello che vede l'utente sia sempre aggiornato con le variabili del modello, senza necessità di andare materialmente nell'oggetto grafico a impostare il nuovo valore.
In più permettono di separare il modello (dove normalmente avvengono le elaborazioni) dall'interfaccia, cosa altamente consigliabile per avere codice riutilizzabile con poco sforzo. Il bello dei bindings è che funzionano a doppio senso; per esempio:

  • se la variabile (property) del mio controller cambia per effetto dell'elaborazione, il campo di testo visibile all'utente e collegato a quella variabile via binding si aggiorna;
  • se l'utente cambia il valore del campo di testo scrivendoci dentro, posso star sicuro che il controller avrà immediatamente la propria property aggiornata con il nuovo valore.
Sempre che la property segua il quadro KVO.
Inoltre, se la property è di un certo tipo e voglio che nell'oggetto grafico ci vada una rappresentazione diversa (p.es. ho un intervallo di tempo che voglio rappresentare sotto forma di colore) basta prevedere un NSValueTransformer che si occupa della traduzione nei due sensi.

Tuttavia, ci sono momenti in cui qualche binding si presenta come la classica mosca al naso (che si vorrebbe distruggere senza esitazione)...
Esempio: voglio un campo di testo che, invece di permettere l'edit diretto, apra una piccola finestra dove l'utente inserisce alcuni ingressi (p.es. cognome, nome, secondo nome) e alla chiusura il tutto viene elaborato e inserito nel campo (p.es. i tre ingressi nella stessa stringa). Dobbiamo usare questo oggetto da più parti, per cui creo una sotto-classe di NSTextField che porti al suo interno tutto il necessario, in modo da poterlo utilizzare dove mi serve semplicemente inserendo la nuova sotto-classe.
Tutto il meccanismo è semplice da implementare; al termine inseriamo la stringa elaborata nel campo, l'utente vede il risultato, ma... la property a cui il campo è collegato tramite binding non si aggiorna! Se invece volessimo aggiornare il modello, bene (anzi male), tale modello non è raggiungibile dalla nostra sottoclasse!

È come se fosse differente inserire a mano (da interfaccia) un valore e inserirlo via codice. E infatti è proprio una cosa diversa, perché da interfaccia il campo riceve un valore e lo passa al binding, mentre se questo avviene da codice non facciamo altro che inserirlo nell'interfaccia e basta!
A pensarci, una volta che si sa, è ovvio! Ma arrivarci... è un'altra cosa.

La soluzione è che dobbiamo scrivere una (piccola) parte del codice che il binding normalmente tratta in automatico, ma che qui non può funzionare perché gli stiamo cambiando i valori da sotto il naso.
Ogni sottoclasse di NSControl (quindi campi di testo, pulsanti, campi data,...) ha un parametro value, che poi è quello che compare nell'inspector di Xcode nella sezione binding. Il nome effettivo è diverso a seconda che sia un numero, una data, ecc...; per un campo di testo è objectValue.
Il codice complessivo è il seguente:
self.objectValue = myNewValue;
NSDictionary *bindingInfo = [self infoForBinding:NSValueBinding];
NSObject *boundObject = [bindingInfo valueForKey:NSObservedObjectKey];
NSString *keyPath = [bindingInfo valueForKey:NSObservedKeyPathKey];
[boundObject setValue:self.objectValue forKeyPath:keyPath];
Quindi, per prima cosa dobbiamo assegnare il risultato che vogliamo inserire proprio a questa proprietà della nostra sottoclasse (riga 1). Una volta fatto, dobbiamo essere noi ad aggiornare la property del modello, visto che il binding non si accorge che gli abbiamo cambiato il valore.
Quindi cominciamo col recuperare informazioni sul binding: in riga 2 otteniamo le info volute (attenzione: se il binding non fosse collegato al value ma ad un'altra proprietà, dovremmo mettere quella al posto della chiave NSValueBinding - le troviamo nella documentazione Apple).
Alla riga 3 otteniamo dal NSDictionary l'oggetto che gestisce il modello, mentre alla riga 4 otteniamo il keyPath (cioè la property) a cui è legato il nostro campo.
A questo punto siamo a posto! Abbiamo l'oggetto e la proprietà: non dobbiamo fare altro che cambiarla: cosa che facciamo nell'ultima riga!
Ora abbiamo cambiato il modello nel modo corretto, per cui eventuali altri binding si aggiorneranno di conseguenza, senza fare altro e se questo valore facesse parte di quanto si andrà a scrivere su disco, i valori registrati saranno corretti!

Nessun commento:

Posta un commento