I have an NSView subclass which has property which I want to be bindable. I've implemented the following in the subclass:
myView.h:
@property (readwrite, retain) NSArray *representedObjects;
myView.m:
@synthesize representedObjects;
+(void)initialize
{
[self exposeBinding: @"representedObjects"];
}
-(void)bind:(NSString *)binding toObject:(id)observableController withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{
if ([binding isEqualToString:@"representedObjects"]) {
[observableController addObserver: self forKeyPath:@"arrangedObjects" options:NSKeyValueChangeNewKey context:nil];
} else {
[super bind: binding toObject:observableController withKeyPath:keyPath options: options];
}
}
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([keyPath isEqualToString:@"arrangedObjects"]) {
[self setRepresentedObjects: [object arrangedObjects]];
}
}
I then create the binding to an arrayController in -[AppController awakeFromNib]
:
[myView bind:@"representedObjects" toObject:arrayController withKeyPath:@"arrangedObjects" options: nil];
Is this the correct way of implementing binding? It involves a lot of boiler plate code which makes me think that I'm doing something wrong.
I thought that NSObject would automagically implement what I have done manually in -bind:toObject:withKeyPath:options:
but this doesn't seem to be the case. If I comment out my -bind:toObject:withKeyPath:options:
the setRepresentedObjects method is never called.
Additional info:
I've done some more investigating and have reached the conclusion that my original approach is correct and you do have to over ride -bind:toObject:withKeyPath:options:
. Here's a quote from Cocoa Bindings Programming Topics: How Do Bindings Work?:
In its bind:toObject:withKeyPath:options: method an object must as a minimum do the following:
- Determine which binding is being set
- Record what object it is being bound to using what keypath and with what options
- Register as an observer of the keypath of the object to which it is bound so that it receives notification of changes
The code sample in Listing 2 shows a partial implementation of Joystick’s bind:toObject:withKeyPath:options: method dealing with just the angle binding.
Listing 2 Partial implementation of the bind:toObject:withKeyPath:options method for the Joystick class:
static void *AngleBindingContext = (void *)@"JoystickAngle"; - (void)bind:(NSString *)binding toObject:(id)observableObject withKeyPath:(NSString *)keyPath options:(NSDictionary *)options { // Observe the observableObject for changes -- note, pass binding identifier // as the context, so you get that back in observeValueForKeyPath:... // This way you can easily determine what needs to be updated. if ([binding isEqualToString:@"angle"]) { [observableObject addObserver:self forKeyPath:keyPath options:0 context:AngleBindingContext]; // Register what object and what keypath are // associated with this binding observedObjectForAngle = [observableObject retain]; observedKeyPathForAngle = [keyPath copy]; // Record the value transformer, if there is one angleValueTransformer = nil; NSString *vtName = [options objectForKey:@"NSValueTransformerName"]; if (vtName != nil) { angleValueTransformer = [NSValueTransformer valueTransformerForName:vtName]; } } // Implementation continues...
This clearly shows that the Joystick class (which is an NSView subclass) needs to override -bind:toObject:withKeyPath:options:
.
I find this surprising. I was skeptical of this conclusion as I have found no other code samples that do this. However, as the offical Apple documentation says I should over ride -bind:toObject:withKeyPath:options:
I conclude that it is the correct approach.
I would be very happy if someone could prove me wrong!