views:

581

answers:

3

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!

+1  A: 

No, you shouldn’t need that glue code.

What do you mean by “doesn’t seem to be the case”? What happens if you omit it?

Ahruman
A: 

I didn't think I needed that code! That leads to the next question: what am I doing wrong?

Well, the fact that you have appear two different names for it may be relevant.
Ahruman
A: 

You definitely DO need to implement -bind:toObject:withKeyPath:options: in a custom view if you want to implement bindings in that view. Your implementation in myView.m is pretty much spot on.

Rob Keniger
No, you don't. You only need to call exposeBinding: and implement KVC- and KVO-compliant accessors for each bindable property. @property and @synthesize do the latter part for you.
Peter Hosey
Interesting, you're right. I've always done it the long-winded way, so I'm off to delete some code.
Rob Keniger