Bindings use KVO to join UI widgets to data directly. Our example from the KVC article, with sliders changing colour components, can actually be done with no code at all, by simply binding the sliders’ values to the colour’s components.
The UI widget classes declare their bindings, largely for the benefit of Interface Builder; pick any widget instance in IB, click on the Bindings tab of the object inspector, and you’ll see them listed. Each one typically represents one piece of the instance’s internal state – its colour, whether its enabled or visible, its font, selection state, etc – and, of course, its value.
Any of them can be bound to any Key Path as long as the types are compatible. Once bound, any change to the UI widget will be pushed to the bound value (by writing the new value to the KVC Key Path); likewise, writing to the value via KVC (or changing it directly, bracketed by willChange/didChange calls) will be Observed, and update the UI.
So, going back to our earlier example, you could plonk down three sliders, inspect each one to find its value binding, and bind to sky.colour.red, sky.colour.blue and sky.colour.green in turn. Drag the sliders, and the colour will be updated live (and presumably you’d see the sky colour change). Change the colour in code, and the sliders will move to reflect this.
The bindings panel contains a lot of stuff, and can be somewhat confusing because it is so generic – it doesn’t really have much “domain knowledge”, it just has a super-generic interface for hooking things together, and is expected to make something of it. So bindings can sometimes be fiddly to set up. Typically, though, they beat the crap out of keeping UI and data in sync by hand.
At the top of the panel, you pick the object to bind to – the object upon which the Key Path will be looked-up; this is usually a controller – often an Array, Tree or Object controller – although it needn’t be. Any variable (or, don’t forget, getter/setter function pair) exposed from your code can be bound to, if there’s an appropriate instance in the Nib.
After picking the target object, you then need to set the model and key paths. I’ve never been quite sure why IB makes the distinction; as far as I can tell, Cocoa just gloms them together and looks it up on the target; eg a model path of sky and a key path of colours.blue maps to the same sky.colours.blue key path we looked at earlier.
At this point, a typical binding is complete. Give it a go.
To handle unusual cases, there’s also a suite of values you can set when the value you’ve bound to is non-standard in some way. For instance, you might want a friendly human-readable label like “No search results found” if the value gets set to nil.
I think it’s worth mentioning that IB provides yet another proxy object, specifically for the purpose of setting up bindings: Shared User Defaults Controller. This is always available in the bindings panel as an object that you can bind to. It represents your application’s preferences file. Yes, writing a “Preferences…” dialog in Cocoa can sometimes be completely code-free: just create a new dialog in IB, lay out some controls, and bind them to the User Defaults. For many common cases, it’s all taken care of.
IB also lets you specify “value transformers” for bindings. A Value Transformer is a mapping function that sits between a UI widget’s binding and the value it’s bound to; values sent through it are transformed before reaching their destination. Typically the reverse transformation is applied going the other way, particular when translating units between different schemes – for instance, Celsius to Fahrenheit and back. Or if your code uses RGB colours, but your UI widgets use HSV colour-space, you could write a transformer that handles the conversion back-and-forth automatically.
Each transformer is a singleton registered with Cocoa, a handful of which are supplied by Cocoa itself, but you can add your own, registering the class when your application starts.
As well as range conversions and such, they can handle other “impedance mismatch” issues with bindings – for instance, if you have a checkbox, its value is a boolean, but you might want it to be checked or not unchecked dependent on whether an object exists or not; one of the default transformers returns true or false according to whether the bound value is nil or not. Obviously, though, this transformation is one-way!
Just a quick final note: There’s nothing particularly magic about bindings, so just because you’re using them, you don’t have to stop using older techniques such as delegates, target-selector actions, and data sources. Sometimes this is essential, as certain Cocoa features aren’t available through bindings.
For instance, if you want to support drag-and-drop from a table, you need to set up a data source for it – even if you’re using bindings to supply the actual data, you can set a data source on it, and Cocoa needs one to handle the tableView:writeRowsWithIndexes:toPasteboard and related messages.
One technique I find helpful, and used in http://voluminous.wooji-juice.com/ Voluminous], is to subclass NSArrayController. I’m getting slightly ahead of myself here, as I haven’t covered these yet, but if you’re using array controllers already, remember that you can do this, and set the custom class of the NSArrayController you’re using in the NIB to be of your new class. Now the same object that’s already handling the data via the binding, can take care of the writing it to the pasteboard too.