Pagine

2012-06-04

Scroll di una NSView

Di solito le scroll-view funzionano da sole: per avere un campo di testo che permetta lo scroll basta inserire da Interface Builder l'oggetto completo e tutto funziona senza fare nulla; lo stesso per una tabella: quando le righe aumentano oltre le dimensioni previste, la NSTableView si porta dietro una scroll automatica.
Se poi vogliamo lo scroll di qualcosa non previsto, basta inserire l'oggetto nella finestra, selezionarlo e poi scegliere il comando XCode Editor > Embed In > ScrollView e vengono inseriti automaticamente gli oggetti giusti e legati fra loro.
Cosa succede invece se devo inserire una scroll su una view generata dinamicamente? In questo caso c'è qualche problemino da risolvere.

Per prima cosa, prepariamo la scrollView che useremo dal codice: aprendo lo xib giusto, trasciniamo dall'elenco degli oggetti una NSScrollView nella finestra; lasciamo tutte le sue impostazioni invariate.
Prepariamo poi una NSView (come consiglia Apple, in un nuovo xib, supponiamo col nome di "myView"): questa sarà la view che caricheremo da codice e che vogliamo abbia la possibilità di avere lo scroll.
Nel codice dell'oggetto che controlla la finestra (di norma sarà l'AppDelegate, ma non è necessario lo sia) avremo un NSViewController, che si occuperà di caricare la view e supponiamo che questo controller sia già inizializzato (come outlet o da codice), chiamiamolo loadedViewController. Nello xib, avremo impostato il File's Owner come NSViewController e collegato l'outlet view alla NSView che caricheremo.
Nel file appDelegate.h avremo qualcosa del tipo:
@property (retain) NSViewController *loadedViewController;
@property (assign) NSScrollView *myScrollView;

- (IBOutlet)caricaView:(id)sender;
mentre nel appDelegate.m supponendo che il caricamento avvenga tramite un click su un pulsante:
- (IBOutlet)caricaView:(id)sender
{
    [NSBundle loadNibNamed:@"myView" owner:loadedViewController];
    NSView *addedView = [loadedViewController view];
    [myScrollView setDocumentView:addedView];
}
La parte importante è stata fatta! Se lanciamo l'applicazione, quando premiamo il pulsante la vista viene caricata (per semplicità non c'è nessun controllo di errore) e se riduciamo la dimensione della finestra, escono fuori le scroll bar laterali, che ci permettono di fare lo scroll!
Ma: attenzione! Proviamo ad allargare molto la finestra e... vedremo che la nuova view resta attaccata al bordo inferiore della scroll view e non al superiore, come sarebbe logico attendersi! Potremmo anche lasciare tutto così, ma gli utenti Mac si aspettano che ogni cosa funzioni in modo logico, per cui questo posizionamento in basso dà fastidio e l'impressione che la nostra applicazione sia buttata lì, senza grande cura. Per cui dobbiamo fare in modo che la view resti in alto.
Cercando in giro per il web, ho trovato l'unico modo è di fare una sottoclasse di NSClipView (che è l'oggetto creato in automatico che fa da tramite tra la NSScrollView e la documentView (cioè quella che viene caricata dinamicamente) e fare in modo che risponda YES alla property Flipped (cioè, facciamo finta che le coordinate siano invertite).
Quindi creiamo i file della sottoclasse:
------- File myClip.h:
#import <Cocoa/Cocoa.h>
@interface myClip : NSClipView
@end
------- File myClip.m:
#import "myClip.h"
@implementation myClip

- (BOOL)isFlipped {return YES;}
@end
L'unico metodo è in fatti isFlipped, che ritorna sempre TRUE e quindi la scroll agisce di conseguenza.
Ora però dobbiamo dire alla nostra scroll di usare questa classe: da Interface Builder non si può, perché la clipView non è accessibile e viene create in automatico durante l'esecuzione. Però... c'è ovviamente un comando che fa la stessa cosa: setContentView.

Riassumendo, il metodo dell'appDelegate diventa:
- (IBOutlet)caricaView:(id)sender
{
    [NSBundle loadNibNamed:@"myView" owner:loadedViewController];
    NSView *addedView = [loadedViewController view];
    [myScrollView setContentView:[[[myClip alloc] init] autorelease]];
    [myScrollView setBackgroundColor:[NSColor windowBackgroundColor]];
    [myScrollView setDocumentView:addedView];
}
dove è stato anche aggiunto il colore di sfondo per la scrollView (altrimenti restava bianco). Abbiamo fatto l'autorelease della clipView, perché viene già ritenuta dalla scrollView.
Se ora lanciamo l'applicazione, vedremo che la view viene caricata in alto e lì resta!