Apple Developer Connection
Advanced Search
Member Login Log In | Not a Member? Support

Binding your Preferences in Cocoa

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.

Model–View–Controller Illustrated

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.

Bindgs—Their Place in Model–View–Controller

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.

Creating a New Project

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.

The Basic Project

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).

The BindPrefs Target

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.

The BindPrefs Target 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.

Interface Builder

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.

The User Interface for Our Application

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.

Connecting the Preferences Menu Item

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.

The Color Well Bindings Info Window

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.

Binding the Color Well to Shared Defaults

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.

Configuring the Slider

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.

Binding the Slider to the Shared Defaults

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.

Our Application, So Far

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.

The Document Window

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.

Binding the Text Field's Size

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.

Binding the Text Field's Color

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.

Our Application, Running

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