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!
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?
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.
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.
来源:https://stackoverflow.com/questions/366938/is-it-necessary-to-override-bindtoobjectwithkeypathoptions-in-an-nsview-subc