
One of the exciting new technologies in Mac OS X v10.3 Panther is Cocoa Bindings. Descended
from technology first introduced in WebObjects and Enterprise Objects frameworks, it has been hailed as a
remarkable replacement for glue code in your applications. Like many Cocoa
technologies, the way Cocoa Bindings works is subtle and requires a bit of practice
before you experience an "ah-ha" moment and get what's going on. Until this
happens, you may be wondering what all the fuss is about. Once you experience
it, though, you won't want to create applications without using this technology.
One of the easiest ways to get started with Cocoa Bindings is to manage
preferences in an application. This article will show you how to using Cocoa
Bindings in a step-by-step fashion to eliminate the glue code that goes with
implementing preferences. At the same time, we hope that the step-by-step format
guides you to your own "ah-ha" moment.
Preferences Described
The ability for a user to set preferences—customizations of the way an
application looks or behaves—is an important part of any application. Years ago,
it was up to you to figure out how to store and retrieve this data. Thankfully,
Cocoa provides a simple and standard way to maintain a user's preferences for an
application called the defaults system.
In a nutshell, the defaults system allows you to store key/value pairs and later
retrieve this information by key. It takes care of the underlying task of
storing and retrieving data for you. This means that you don't have to deal with
creating a data format to hold the preferences. It also means that preferences
will be stored in a set of standard locations which lets users easily work with
them either directly, or by using the defaults command-line tool (you'll use the defaults command later on).
The preferences system neatly solves the various issues around persisting data
between application invocations and lets you access that data from the various
parts of your application that need it. For example, you could create a
preference window that lets the user select the color and size of various
user-interface text elements. Once you had the preference window, you could then
write the code to have changes in the preference window be saved into the
preferences system. The various components of the application could likewise be
set by writing glue code which reads from the preference system the size and
color to set text attributes.
Let's take a minute and put this discussion in terms of Model-View-Controller—a
basic design pattern upon which quite bit of Cocoa is fashioned. The various
views of our application, such as the preferences window and the containers for
the various user-interface elements are the View. The preferences system is the
Model. In between the View and Model is the Controller, as shown in Figure 1. It is in this
in-between Controller layer that glue code is needed to hook up the user
interface and the preference system.

Figure 1: Model–View–Controller Illustrated.
Writing the glue code described so far isn't a terribly complicated undertaking.
A sticky issue remains, however. When a preference is changed, say by a user
using the application's preferences window, the various parts of an application
that use that preference won't automatically know that the preference has been
updated. This means that a user's preference setting in the application's
preferences window won't be automatically visible in the running application, but
will only be visible the next time the application is launched. Obviously, this
would be a subpar user experience.
Enabling live updates of the affected user interface components using glue code
turns out to be a bit of an undertaking. You can make sure that the
preferences window somehow has a reference to the various user interface
components that need to be updated—not always practical—or you can use
notifications and make sure that every UI component has a controller that has
registered for the appropriate notifications and will update the user interface
when changes are made.
What starts out as a simple matter of glue code can get quite involved and become a long, hard road. As of
Mac OS X v10.3 Panther though, we don't have to travel it. We can instead eliminate almost all
of the preference handling glue code and get automatic updating of UI elements
that rely on preferences. To do this, we use Cocoa Bindings.
What Are Cocoa Bindings?
Cocoa Bindings is a new set of technologies that snap right into the
Model–View–Controller design pattern. It lets you hook up various View and Model
objects and takes care of keeping them in sync. It does this by keying in a
property name. When a view element is set up with a particular property name and
a model object is also set up with the same property name, a binding can be made
between the two. This will ensure that a value displayed
on screen stays in sync with a particular value in the model.
One of the new classes in Cocoa, provided as part of Cocoa Bindings, is the
NSUserDefaultsController class. This class makes an appearance as the Shared
Defaults object in Interface Builder and allows view elements to be bound to
property names that are stored in the preferences system, replacing glue code,
as shown in Figure 2.

Figure 2: Bindings: Their Place in Model–View–Controller.
Whenever the user manipulates the control, the binding will take care of making
sure that the appropriate setting in the underlying defaults system is updated.
We can also create a binding from a key in the defaults system to a UI element
in the main user interface. This means that we do not have to write any
code to set a preference; further, it means we don't need to write any code to ensure that
the settings made in the preference window are instantly propagated through the
application.
In other words, the glue code can go away. And code that isn't written
doesn't have to be maintained or tested nor does it obscure the code that we
really need to write—the code that provides the unique functionality of
our application.
Hands-on Binding Preferences
To show how to bind user interface elements to the preference system, let's
actually get our hands dirty. We're going to build a simple little application
that allows a user to set preferences on what color and size text should be
displayed in the application's windows. To show how Cocoa Bindings can
automatically update properties of view objects in multiple places at once,
we're going to build a document-based application.
Start up Xcode and create a new project (using the File > New Project menu).
In the New Project Assistant, make sure to select "Cocoa Document–based
Application", as shown in Figure 3.

Figure 3: Creating a New Project.
You can save the project with whatever name you would like and into any folder.
In this example, we have used "BindPrefs" as the project name. Once you have
created the project, the main project window will appear, as shown in Figure 4.

Figure 4: The Basic Project.
The first bit of work we need to do is to change our application's
identifier. By default it is set to "com.apple.myApp". Instead of this generic
name, we want to give it a more appropriate identifier. Setting the identifier
is important because it is used by the defaults system to keep preferences of
one application separate from another. It also serves as the name of the file in
the ~/Library/Preferences folder.
To set the identifier, open up the Targets group in the Project window, as
shown in Figure 5, and then open its Info window (use the Project > Get Info
menu or the Command–I keyboard shortcut).

Figure 5: The BindPrefs Target.
In the info window, select the Properties tab and change the Identifier to
"com.sample.BindPrefs", as shown in Figure 6. Once you've made this change you
can close the Info window.

Figure 6: The BindPrefs Target Info Window.
Setting Up The Preference Window
Now, it's time to set up the user interface and define some bindings. First
we're going to set up the Preference window for the application. This window is set
up in the applications Main nib file. To open this file, simply double-click the
MainMenu.nib file Xcode's Project window. Interface Builder will launch and
you'll see the windows shown in Figure 7 appear.

Figure 7: Interface Builder.
To create a panel for our preference window, drag a Panel object out from the
Cocoa–Windows palette. A blank panel will appear. Onto this panel, drag out
a color well and a slider from the Cocoa–Controls palette. Add a few text
fields and arrange the panel to look something like Figure 8. Don't worry, you
don't have to exactly match what is shown here—we're not going to try to
win any design awards here.

Figure 8: The User Interface for our Application.
Next, in order to make the panel appear when the user selects the Preferences
menu item, we need to make a connection between the application's Preferences
menu item and the panel. To do this, Control–drag a connection from the
Preferences menu item to the Panel in the MainMenu.nib window. Then, connect it
to the "makeKeyAndOrderFront:" action in the Info window, as shown in Figure 9.

Figure 9: Connecting the Preferences Menu Item.
Now, we just need to set up the controls in our preferences window and bind them
to the defaults system. Select the color well control and then in the Info
window, select Bindings from the pull-down menu at the top. This will give
access to the various bindings that we can perform on the color well, as shown
in Figure 10.

Figure 10: The Color Well Bindings Info Window.
The Bindings Info window lists the various properties of the selected object
that we can bind to. In the case of the color well, we have access to the value
of the color well and we can decide whether or not the control is enabled or hidden.
Click on the value entry in the Value category and perform the following steps:
- Click the Bind checkbox. When you do this, you'll see a "Shared
Defaults" object appear in the
MainMenu.nib window. This indicates to you
that there are objects bound to the defaults system.
- Make sure that the Bind to pull down menu is set to "Shared User
Defaults". This operation binds the color well to the defaults system using an
instance of the
NSUserDefaultsController class.
- Make sure that the Controller Key is set to "values".
- Type in "textColor" into the Model Key Path text field. This is the key
in the defaults system that we want to store the color information into.
- Set the Value Transformer to "NSUnarchiveFromData". You can access this
value using the pull-down button at the end of the text field. We have to
set this because the value of the color well is a NSColor object. The
defaults system can't handle NSColor objects so this setting will use an
archiver to encode the color selected into a form that can be handled by
the defaults system.
The result of these steps is shown in Figure 11.

Figure 11: Binding the Color Well to Shared Defaults.
Next, we are going to set up the size slider. Because we're going to be
setting the size of text using this slider, we need to set up some sane values.
Select the slider and bring up the Attributes Info panel. Then perform the
following steps:
- Set the minimum value to 8.0
- Set the maximum value to 20.0
- Set the current value to 12.0
The results of these steps is shown in Figure 12.

Figure 12: Configuring the Slider.
Next, bring up the Bindings Info panel for the slider. Just as with the color
well, we're going to make a few settings to bind it to a key in the defaults
system. Click on the value entry and perform the following steps:
- Click the Bind checkbox.
- Make sure that the Bind to: pull-down menu is set to "Shared User
Defaults". Once again, operation binds the object to the defaults system.
- Make sure that the Controller Key is set to "values".
- Type in "textSize" into the Model Key Path text field. This is the
key in the defaults system that we want to store the color information
into.
Because the output from the slider is a number, which the defaults system
can handle, there is no need for a value transformer. The results of these steps
is shown in Figure 13.

Figure 13: Binding the Slider to the Shared Defaults.
Now, we're done with the application's Preference pane. Let's perform a test
to take a look at what we've done so far.
Running the Application for the First Time
Save the nib file. A dialog box will pop up saying that the nib file contains
features that can't be saved in a format that is compatible with Mac OS X prior
to version 10.2. Click on the Save in 10.2 format button.
Next return to Xcode and build and run the application. You can do this by
clicking on the Build and Run icon in the toolbar of the Project window, using
the Build > Build and Run menu, or using the Command–R keystroke. When you
do this, you'll see a document window pop up—the default document window
in the project template. We haven't touched it yet, so it's rather boring. To
see the results of our handy work, use the Preferences menu and bring up the
panel. Click on the color well and set the color to whatever you'd like, as
shown in Figure 14.

Figure 14: Our Application, So Far.
This might not seem too impressive so far but here's where it gets
interesting. Quit the application and then relaunch it. Open the preferences
panel again and you'll notice that the color is just as you left it. Play with
this a bit by quitting and running the application a few times. Tweak the slider
and the color well each time to different values.
What we've done is create a preferences window with absolutely no code by
leveraging Cocoa Bindings. To prove that values really are going in and out of
the defaults system, open up a Terminal window and type in the following:
$ defaults read com.sample.BindPrefs
What displays is:
{
NSColorPanelMode = 6;
NSColorPanelVisibleSwatchRows = 1;
"NSWindow Frame NSColorPanel" = "751 275 201 309 0 0
1280 832 ";
textColor =
<040b7479 70656473 74726561 6d8103e8 84014084 8484074e
53436f6c
6f720084 84084e53 4f626a65 63740085 84016301 84046666 66
66833f 51aa4883
3f09523b 833edb7f a50186>;
textSize = 14.5625;
}
Notice that we used the same application identifier that we set on the
BindPrefs target. Also notice the values for our two keys, textColor and textSize. The
textColor key contains a data block that a NSColor object can be created from.
The textSize key contains a simple number. The values for these two keys
probably won't match what you see in the output above, but will reflect the
settings you made while tweaking the controls in the preference window. In
addition, there will probably be a few other keys in the output like
NSColorPanelMode and NSColorPanelVisibleSwatchRows. These are set by the color
picker so that the next time you open it, it will be as you left it.
Now that we've made a preference window that reads and writes values to the
defaults system, let's modify the document window so that we can see these
values propagate.
Setting up the Document Window
To set up the document window, double click the MyDocument.nib in the Xcode
Project window. The default document window will appear. Click on the "Your
document contents here" string and bring up the Bindings Info panel, as shown
in Figure 15.

Figure 15: The Document Window.
In this Bindings Info window, you'll notice that there are several more
things that Cocoa Bindings can do with a text string. Since our preferences window
stores values for textSize and textColor keys, we're going to bind the fontSize
and textColor properties of the string to these keys.
Open the fontSize entry and perform the following steps:
- Click the Bind checkbox.
- Make sure that the Bind to: pull-down menu is set to "Shared User Defaults".
- Make sure that the Controller Key text field is set to "values".
- Set the Model Key Path text field to textSize. You shouldn't have to
type this key from memory. Instead, you may be able to click the pull-down
button at the end of the text field. This will give you a list of the
keys that you can bind to (unless you have quit Interface Builder).
The result of these steps is shown in Figure 16.

Figure 16: Binding the Text Field's Size.
Next, open the textColor entry, found under Text Color. You may need to
scroll down the Info window a bit to see it. Perform the following steps.
- Click the Bind checkbox.
- Make sure that the Bind to: pull-down menu is set to "Shared User Defaults".
- Make sure that the Controller Key is set to "values".
- Set the Model Key Path text field to "textColor".
- Set the Value Transformer to "NSUnarchiveFromData". Remember, we had to use this in the preference window because the defaults system can't store color objects natively. Because the value is archived into the defaults system, we need to unarchive it at this point.
The result of these steps is shown in Figure 17.

Figure 17: Binding the Text Field's Color.
We're done. Once again, we haven't written any code, but we've actually put a
lot of functionality into the application. Let's set it in action.
Running the Application, Again
Save the MyDocument.nib file. Once again, you'll be warned about the 10.2 nib
file format issue. Click the Save in 10.2 Format button. Next, return to Xcode
and then build and run the application (Command–R).
You'll probably notice that the text is no longer black, but is the color that
you last left the preferences window in. Pop open the preferences window and select
a new color. Notice that the color of the text in your document window tracks
the color changes, as shown in Figure 18. Next, play with the font size. Pop open
a new document window and watch the updates happen in multiple windows at once.

Figure 18: Our Application, Running.
Remember how we said at the beginning of this article that, in order to keep
all of the places where preferences were used in an application in sync, it once
required quite a bit of glue code? Using Cocoa Bindings, no glue code was
created. If you've been keeping score, you'll notice that not one line of code
has been written.
Caveats
Now, before you get too excited, there are a few limitations to using Cocoa
Bindings in your application's preference handling. The first is that Cocoa
Bindings only ships with Panther. If you are creating a new application, this
might not be a problem. If you are creating an application that must run under
Mac OS X 10.2 or earlier, however, Bindings are of no help.
Also, Cocoa Bindings is a new technology and is in the process of being
worked into the system. Many of the properties in Application Kit controls are
exposed to Cocoa Bindings, but there are many that aren't yet. It is possible to
bind to properties that aren't exposed, but this does require a bit of code on
your part. However, as Cocoa Bindings matures in the future, hopefully we'll see
more and more access to various UI component properties.
For More Information
Updated: 2005-01-05
|