Alcune volte è necessario presentare un oggetto generico che eredita da UIView in maniera esclusiva sullo schermo, per richiamare l’attenzione o semplicemente per mostrare una scelta “obbligata” all’utente. UIKit di Cocoa, ci mette a disposizione la classe UIAlertView proprio per questo scopo. Purtroppo, il suo funzionamento è molto restrittivo in termini di personalizzazione da parte dell’utente e come funzionalità offerte.
In quest’articolo, sfrutteremo questa esigenza per prendere confidenza con una tecnica di programmazione molto particolare di Objective-C: Le categorie.

Introduzione alle categorie
Le categorie sono un potente strumento, fornito agli sviluppatori Objective-C, per estendere le funzionalità degli oggetti messi a disposizione del programmatore. A differenza delle sottoclassi, permettono di specializzare il comportamento di un oggetto senza dover scendere in dettagli tecnici relativi alla classe stessa. Una categoria permette di estendere immediatamente il comportamento di una determinata classe; l’estensione sarà immediatamente disponibile in tutte le classi (e sottoclassi) che ereditano dall’oggetto di cui stiamo estendendo le funzionalità.
Come in Javascript con l’estensione dei metodi, le categorie permettono al programmatore di aggiungere funzionalità al codice di una classe, senza per questo esserne gli sviluppatori originali e/o in possesso del codice sorgente. In linea di principio, è possibile estendere il comportamento di una classe/oggetto, semplicemente creando una categoria per questo scopo. A differenza del concetto di sottoclasse, le categorie possono essere cumulative, nel senso che più categorie possono essere sommate per aggiungere funzionalità diverse alla stessa classe. Quindi, una classe può essere estesa contemporaneamente da più categorie che, insieme, concorrono a fornire nuove caratteristiche all’oggetto base.
Per creare una categoria è necessario implementare una porzione di codice dichiarativo (header file .h) ed una porzione di codice implementativo (implementation file .m). Il nostro file di header, quindi, conterrà un prologo dichiarativo su quello che stiamo per fare; in pratica, informeremo il compilatore che stiamo per estendere la classe UIView, ad esempio, con delle nuove funzionalità (metodi d’istanza). Conseguentemente, il file di implementazione conterrà l’implementazione di questi metodi e delle funzionalità dichiarate in precedenza.
Nuove funzionalità per le UIView
Ogni oggetto che eredita da UIView può essere aggiunto, a sua volta, come subview all’interno di un’altro oggetto UIView padre, attraverso l’uso del metodo d’istanza: -(void)addSubview:
Il nostro obiettivo è quello di aggiungere una specializzazione a questo comportamento per presentare il nuovo oggetto in maniera gradevole, ad esempio mostrandolo come pop-up al centro dello schermo e rendendolo reattivo al tocco dell’utente. Alla pressione su di esso, infatti, vorremmo che scompaia in maniera altrettanto “gradevole” dallo schermo. La figura sotto, illustra il concetto che stiamo per realizzare programmaticamente.

Visto che non abbiamo ancora chiaro da “quale” classe erediteremo per il nostro oggetto modale da presentare o, semplicemente, se vogliamo dotare “tutti” gli oggetti che ereditano da UIView di queste caratteristiche, utilizzeremo una categoria. Quest’ultima estenderà il comportamento di tutti gli oggetti che ereditano da UIView, in maniera istantanea, con due nuovi metodi:
-(void) pushViewTo:(UIView *)aView;
-(void)removeFromView;
Il primo metodo si occuperà di mostrare il nostro oggetto all’interno del contenitore padre passato come parametro. Il secondo metodo, si preoccuperà di rimuoverlo dal contenitore padre e distruggerlo dalla memoria. La parte di dichiarazione, che andrà nel file di hedear (.h), sarà così composta:
@interface UIView (animation)
-(void) pushViewTo:(UIView *)aView;
-(void)removeFromView;
@end
I metodi dichiarati sono gli unici pubblici e, quindi, gli unici che verranno esposti a tutte le classi estese dalla categoria. La prima riga ci dice che stiamo estendendo la classe UIView, creando una categoria chiamata animation. Le successive due righe, racchiuse prima della parola chiave @end, indicano i metodi che creeremo nell’apposito file d’implementazione.
A differenza delle normali dichiarazioni di interfacce di classe, qui non è possibile dichiarare delle variabili di istanza. L’unica cosa che possiamo fare è dichiarare i metodi che popoleranno la nostra categoria e che andranno ad estendere il comportamento standard della classe. La parte implementativa (.m) sarà costituita di conseguenza:
@implementation UIView (animation)
-(void) pushViewTo:(UIView *)aView {
…
…
}
-(void)removeFromView {
…
…
}
@end
Popoliamo i nostri metodi
A questo punto resta da implementare il codice per ognuno dei metodi sopra. In quest’esempio, vogliamo che l’oggetto UIView, compaia dal centro dell’oggetto padre e si ingrandisca gradualmente, con un effetto molla:
-(void) pushViewTo:(UIView *)aView {
//Creiamo una matrice di trasformazione che scala la dimensione originale a zero
CGAffineTransform transform = CGAffineTransformMakeScale(0.1, 0.1);
self.transform = transform;
//Abilitiamo l'interazione utente sull'oggetto
self.userInteractionEnabled = YES;
//Aggiungiamo l'oggetto al padre (aView) e posizioniamolo al centro
[aView addSubview:self];
self.center = CGPointMake(160, 240);
//Iniziamo un contesto CoreAnimation
[UIView beginAnimations:@"pushViewToScreen" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.3];
//Impostiamo come delegato la categoria stessa!
[UIView setAnimationDelegate:self];
//Al completamento della prima parte dell'animazione, lanciamo la seconda.
[UIView setAnimationDidStopSelector:@selector(pushViewToSecondPart)];
// Tutto il codice racchiuso nel contesto d'animazione verrà automaticamente
// animato con keyframes.
CGAffineTransform transform2 = CGAffineTransformMakeScale(1.1, 1.1);
self.transform = transform2;
[UIView commitAnimations];
}
Resta da implementare il metodo di scomparsa dallo schermo: -(voi)removeFromView. Per ottenere un simpatico effetto molla, dobbiamo “spezzare” l’animazione in tre fasi distinte: ingradimento, rimpicciolimento, scomparsa.
-(void)removeFromView {
[UIView beginAnimations:@"removeFromView" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.3];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(removeFromViewSecondPart)];
CGAffineTransform transform2 = CGAffineTransformMakeScale(1.1, 1.1);
self.transform = transform2;
[UIView commitAnimations];
}
-(void)removeFromViewSecondPart {
[UIView beginAnimations:@"removeFromViewSecondPart" context:nil];
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut];
[UIView setAnimationDuration:0.3];
[UIView setAnimationDelegate:self];
[UIView setAnimationDidStopSelector:@selector(removeFromViewThirdPart)];
CGAffineTransform transform2 = CGAffineTransformMakeScale(0.1, 0.1);
self.transform = transform2;
[UIView commitAnimations];
}
-(void)removeFromViewThirdPart {
[self removeFromSuperview];
}
Ancora una cosa: Il controllo del tocco. Categorie e delegati
Resta da implementare un’ultima caratteristica. L’oggetto presentato a schermo dovrebbe rispondere al tocco dell’utente, reagendo con la scomporsa / rimozione di se stesso. Questo ci offre un utile spunto per illustrare un’interessante caratteristica delle categorie.
Le categorie, come una normale classe, possono implementare metodi delegati rendendoli disponibili a tutte le classi (o sottoclassi) che stiamo estendendo. Categorie e delegati, quindi, possono coesistere ed insieme possono collaborare nell’estendere il comportamento di una classe. Nel nostro caso specifico, implementeremo all’interno della categoria un metodo delegato per la gestione del tocco utente:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
Ci basterà verificare che il tocco sia stato effettuato sull’oggetto stesso (self) ed, in caso affermativo, lanciare il metodo di rimozione dallo schermo definito sopra:
#pragma mark UITouch events
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
if (touch.view == self)
[self removeFromView];
}
A questo punto, il nostro oggetto UIView è stato specializzato con una categoria animation che ne rende l’apparizione e la scomparsa dallo schermo molto gradevole. Visto che abbiamo esteso il comportamento della classe UIView, qualunque altro oggetto che eredita da UIView, potrà usare i nostri nuovi metodi di apparizione e scomparsa. Ad esempio, un oggetto UIImageView le potrà sfruttare a suo vantaggio per mostrare un’immagine a schermo. Nel codice d’esempio fornito troverete due esempi, uno con UIView ed uno con UIImageView. Entrambi gli oggetti ereditano le stesse primitive.
L’uso delle categorie non si ferma solo a questo ma, avere ben chiaro qual’e’ lo scopo principale per cui è stato pensato questo pattern di programmazione, renderà più semplice l’apprendimento di tecniche avanzate d’uso.
Non mi resta che salutarvi e ringraziarvi per l’attenzione. Buone feste e felice anno nuovo!
Costantino Pistagna










2 Responses to “T#083 – Aggiungiamo funzionalità agli oggetti: Le categorie”
31 Dicembre 2010
iPhoneInsideArticolo interessante !
Dato che si parla di gestione del touch, mi sapreste aiutare con un piccolo problemino?
Nella mia applicazione creo un numero di UIImageView e ne memorizo il puntatore in un array.
Nell’evento “touchesBegan”, come faccio a sapere quale immagina è stata “toccata” ?
Con cosa devo confrontare “touch.view” ?
Grazie 🙂
4 Gennaio 2011
EthaSe dai un’occhiata all’ultimo pezzo di codice dell’articolo, c’è scritto proprio ciò che ti serve. 😉
…
UITouch *touch = [touches anyObject];
if (touch.view == self) {
//doSomething
}