14

I'm a beginner when it comes to writing Mac apps and working with Cocoa, so please forgive my ignorance.

I'm looking to create a custom view, that exposes some properties, which I can then bind to an NSObjectController. Since it's a custom view, the Bindings Inspector obviously doesn't list any of the properties I've added to the view that I can then bind to using Interface Builder.

After turning to the Stackoverflow/Google for help, I've stumbled across a couple of possible solutions, but neither seem to be quite right for my situation. The first suggested creating an IBPlugin, which would then mean my bindings would be available in the Bindings Inspector. I could then bind the view to the controller using IB. Apparently IBPlugins aren't supported in Xcode 4, so that one's out the window. I'm also assuming (maybe wrongly) that IBPlugins are no longer supported because there's a better way of doing such things these days?

The second option was to bind the controller to the view programmatically. I'm a bit confused as to exactly how I would achieve this. Would it require subclassing NSObjectController so I can add the calls to bind to the view? Would I need to add anything to the view to support this? Some examples I've seen say you'd need to override the bind method, and others say you don't.

Also, I've noticed that some example custom views call [self exposeBinding:@"bindingName"] in the initializer. From what I gather from various sources, this is something that's related to IBPlugins and isn't something I need to do if I'm not using them. Is that correct?

I've found a post on Stackoverflow here which seems to discuss something very similar to my problem, but there wasn't any clear winner as to the best answer. The last comment by noa on 12th Sept seems interesting, although they mention you should be calling exposeBinding:. Is this comment along the right track? Is the call to exposeBinding really necessary?

Apologies for any dumb questions. Any help greatly appreciated.

Community
  • 1
  • 1
dbr
  • 487
  • 4
  • 10

2 Answers2

16

The first suggested creating an IBPlugin, which would then mean my bindings would be available in the Bindings Inspector. I could then bind the view to the controller using IB. Apparently IBPlugins aren't supported in Xcode 4, so that one's out the window.

Correct. Interface Builder is dead; long live the Xcode nib editor (which they still call Interface Builder sometimes).

With IB gone, so are IBPlugins.

I'm also assuming (maybe wrongly) that IBPlugins are no longer supported because there's a better way of doing such things these days?

Nope.

The second option was to bind the controller to the view programmatically. I'm a bit confused as to exactly how I would achieve this.

Send the view a bind:toObject:withKeyPath:options: message.

Would it require subclassing NSObjectController so I can add the calls to bind to the view?

Not NSObjectController, but something that either owns the nib (such as a window controller or view controller) or is a top-level object inside it (such as the application's delegate in the MainMenu nib).

Would I need to add anything to the view to support this?

See below.

Some examples I've seen say you'd need to override the bind method, and others say you don't.

You used to, for non-views (views always worked without overriding it), but not anymore. You no longer need to override the bind:::: method.

I don't know when this changed, but I wrote a test app to confirm the current behavior (as of Snow Leopard and Lion).

Also, I've noticed that some example custom views call [self exposeBinding:@"bindingName"] in the initializer. From what I gather from various sources, this is something that's related to IBPlugins and isn't something I need to do if I'm not using them. Is that correct?

Nope.

You don't need to override bind:::: to bind to any KVC-/KVO-compliant property, and you don't need to send exposeBinding:.

Confusingly, the documentation says otherwise: that you must override bind:::: and unbind:, even in views, and that exposeBinding: is useful for anything.

All you have to do to create an available binding is implement a KVC-/KVO-compliant property. If it's a synthesized @property, this is done. Otherwise, see here.

Then, send the view/object a bind:::: message to actually bind it, since there's no way to expose the binding in the nib editor.

TL;DR:

Just implement a regular property, and you'll be able to bind it with a bind:toObject:withKeyPath:options: message (at least in Snow Leopard and Lion). You don't need to send exposeBinding: from anywhere anymore. You can't make custom bindings show up in the nib editor in Xcode 4.

Peter Hosey
  • 95,783
  • 15
  • 211
  • 370
  • Thanks Peter, that's helped me out a lot. If I'd like my view to redraw when a property value is changed via a binding, am I going to have to create a custom setter that calls setNeedsDisplay, rather than using @synthesize? – dbr Nov 12 '11 at 21:19
  • @Newt: Yup, and you should accordingly declare the property as `nonatomic`. You can still synthesize the instance variable and getter, but you will need to implement a custom setter. – Peter Hosey Nov 12 '11 at 21:27
  • 1
    I also noticed that the documentation for `bind:toObject:withKeyPath:options:` implies you need to call `exposeBinding:`, when it says "...a property of the receiver previously exposed using the exposeBinding: method." when clearly that isn't necessary. – dbr Nov 13 '11 at 15:53
  • 1
    @PeterHosey quick question, Tom Dalling in 2009 wrote "Using the default NSObject implementation, changes in the model will update the view, but the reverse is not true." Is this still the case - is extra work needed to have it work both ways? – Vervious Jan 05 '12 at 01:41
  • 1
    @Nano8Blazex: Yes. I just tried it, and changing the value in a bound object (view or otherwise) does not propagate the change to bound-to objects (e.g., in the model). – Peter Hosey Jan 05 '12 at 22:06
  • Wow, thanks @PeterHosey. I wish I knew that this SO post existed before reading the docs on bindings and implementing all that unnecessary code in my NSView subclass! – Diggory Mar 20 '15 at 17:18
  • 1
    Also - note that Peter's demo doesn't get you bi-directional binding for free - you need to manually bind both ways (e.g. I added a keyDown: method in the HueView to increment the hueDegrees on any key press. That did not update the textfield until I added the following: `[self.valuesOwner bind:@"hueDegrees" toObject:self.hueView withKeyPath:@"hueDegrees" options:nil];` – Diggory Mar 20 '15 at 17:20
  • @PeterHosey is there a way to show custom bindings in Xcode 7 or that didn't change? – Ian Bytchek Jan 10 '16 at 19:12
  • @IanBytchek: In the nib editor, you mean? That hasn't changed, as far as I know. – Peter Hosey Jan 12 '16 at 04:17
  • Storyboards actually, but I don't think it's any different. I'm talking about custom controls with custom defined properties. Like `NSTextField` would have a `value` binding, and I want `FooView` to have `bar` binding. – Ian Bytchek Jan 12 '16 at 04:50
6

To elaborate on / clarify / pontificate upon @PeterHosey's answer.. i took the liberty of forking his test app in efforts to make clear the ONLY way I have been able to figure out to bind a view's properties... in the "modern" (not), Post-IBPlugin (RIP) era... The entire app is done in 3 methods in a subclassed NSView with some bindings attached.

enter image description here

In order for a view to "update the view" without having to mess around with the hueDegrees property's setter.. I just do this...

- (void) didChangeValueForKey:(NSString*)key {
     self.needsDisplay = YES; [super didChangeValueForKey:key]; }

By doing that, and setting the initial value for the property in IB like so...

enter image description here

You eliminate a lot of glue code. In order to have the "Hue" NSTextField properly update... since you can't "bind" to a view.. I simply drag an NSViewController in the "Objects area", and connect the view controller's View outlet to the view in IB. Then create your binding via the view controller.

enter image description here

Hope this helps clarify - with a quite simple solution - this poorly-documented / commonly-confused issue.

Alex Gray
  • 16,007
  • 9
  • 96
  • 118