6

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!

Community
  • 1
  • 1
  • Related question with more activity, on bindings with two-way updates: http://stackoverflow.com/questions/1169097/can-you-manually-implement-cocoa-bindings – paulmelnikow Sep 12 '11 at 22:41

3 Answers3

1

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?

Jens Ayton
  • 14,532
  • 3
  • 33
  • 47
1

No, it is not necessary to override bind:.

As Peter Hosey wrote in the comment to the earlier answer, you can call exposeBinding: and implement KVC- and KVO-compliant accessors and setters.

MyView.h:

@interface MyView : NSView {
    NSArray *_representedObjects;
}

// IBOutlet is not required for bindings, but by adding it you can ALSO use
// an outlet
@property (readonly, retain) IBOutlet NSArray *representedObjects;

@end

MyView.m:

+ (void)initialize {
    [self exposeBinding:@"representedObjects"];
}

// Use a custom setter, because presumably, the view needs to re-draw
- (void)setRepresentedObjects:(NSArray *)representedObjects {
    [self willChangeValueForKey:@"representedObjects"];
    // Based on automatic garbage collection
    _representedObjects = representedObjects;
    [self didChangeValueForKey:@"representedObjects"];

    [self setNeedsDisplayInRect:[self visibleRect]];
}

Then you can set the binding programmatically:

[myView bind:@"representedObjects" toObject:arrayController withKeyPath:@"arrangedObjects" options: nil];

To set the binding in Interface Builder, however, you must create a custom palette.

paulmelnikow
  • 16,895
  • 8
  • 63
  • 114
0

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
  • 45,830
  • 6
  • 101
  • 134
  • 3
    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 Jan 08 '09 at 04:29
  • Interesting, you're right. I've always done it the long-winded way, so I'm off to delete some code. – Rob Keniger Jan 08 '09 at 07:58