2010 07 20Binding through NSApp
AppleScript offers a delegate method for NSApp
to handle custom keys. That's how you can ask iTunes the name of current track
, even though there's no such key in NSApplication
— it's made available through application:delegateHandlesKey:
Good news is, you don't have to use AppleScript to enjoy the benefits of extra keys ! NSApp
can become a way of sharing instances across NIBs, or a central repository of data.
How ?
First, declare the keys we're using in an array and allocate a dictionary to store their values :
@implementation ApplicationController - (id)init { self = [super init]; if (!self) return nil; // A NSMutableArray recording keys we're using handledKeys = [[NSMutableArray alloc] initWithObjects:@"myCustomKey", @"anotherKey", @"etc", nil]; // A NSMutableDictionary storing our custom keys' values appDict = [NSMutableDictionary new]; return self; }
Then, tell NSApp
what keys we're handling :
- (BOOL)application:(NSApplication *)sender delegateHandlesKey:(NSString *)key { return [handledKeys containsObject:key]; }
Finally, implement get and set methods for our keys. Don't bother writing accessors, just implement those in the fallback KVC methods :
- (id)valueForUndefinedKey:(NSString *)key { return [appDict valueForKey:key]; } - (void)setValue:(id)value forUndefinedKey:(NSString *)key { // Notify change will happen [NSApp willChangeValueForKey:key]; [appDict setValue:value forKey:key]; // Notify change did happen [NSApp didChangeValueForKey:key]; }
Binding in Interface Builder
Once your delegate handles custom keys, bind in Interface Builder like this :
Update the key with [NSApp setValue:@"hello" forKey:@"myCustomKey"]
.
Binding manually
For custom controls, call bind like this :
[myControl bind:@"controlKeyPath" toObject:NSApp withKeyPath:@"myCustomKey" options:nil];
Binding or sharing data across NIBs
Binding to NSApp
using mainWindow.windowController.document
will give access to the current document. To implement an inspector, load an external NIB and bind to that keyPath. Another way is to set a custom key : when selecting an object, update a key containing the selected object [NSApp setValue:selectedObject forKey:@"selectedObject"]
and bind the inspector to it.
Using keypaths
To work for keypaths, that is container.values.leafValue
instead of just myCustomKey
, first create containers :
// Create containers for leaf keys [NSApp setValue:[NSMutableDictionary dictionary] forKey:@"container"]; [NSApp setValue:[NSMutableDictionary dictionary] forKeyPath:@"container.values"];
Then bind and update just as you would a key :
// Bind [myControl bind:@"controlKeyPath" toObject:NSApp withKeyPath:@"container.values.leafValue" options:nil]; // Then update model value and watch it change everywhere it's bound [NSApp setValue:@"Hello !" forKeyPath:@"container.values.leafValue"];
You can. This post is an offshoot of exploring AppleScript, how it actually makes for (I think) simpler design even when not using it.
I haven't tried it, but can't you just bind to NSApp with a key path of
@"delegate.myCustomKey"
? No containers needed that way for longer key paths.