mac_ch13.eps
co_bar.eps

Chapter 13: Using Preferences and Bindings

mac_chbox13.eps

In This Chapter

Understanding bindings

Using bindings with controllers

Implementing preferences with bindings

Creating and using value transformers

In the same way that space is big, bindings are complex. Apple's documentation might not be considered a model of clarity, and it's difficult to find good information online from other sources, in part because bindings cause so much confusion to so many developers that even Cocoa experts have trouble with them. This chapter is designed to cut through the confusion that surrounds bindings and present them in a simple and clear way. To use bindings successfully you must understand:

bl.eps What bindings are

bl.eps How bindings relate to other technologies, such as properties, Key-Value Observing (KVO), and Key-Value Coding (KVC)

bl.eps How bindings are implemented in code

bl.eps How bindings are implemented in Interface Builder (IB)

bl.eps How bindings are connected to controller objects

bl.eps How bindings and controllers collect and transform values

Fortunately expert-level insight into each of these features isn't required.

Understanding Bindings

Bindings are a step up from Key-Value Observing (KVO). They link object properties. When one property changes, the property or UI object at the other end of the binding also changes. But bindings add intelligence to KVO. With some care, it's possible to bind strings to numbers, and vice versa.

Updates can be read-only, with one object — such as a User Interface element in a view — observing and reporting the value of another. They can also be read-write; changing the value of one object automatically changes the corresponding value in another. When bindings are set up correctly, this happens without code.

Bindings are typically used in two ways:

bl.eps In a simple application, they can take the place of outlets. UI objects can be automatically connected to code objects without declaring any IBOutlets or linking them in IB. The binding links the two objects, with optional added intelligence. For example, a text field can display a number value automatically, without explicit conversion.

bl.eps In a more complex application, bindings are used as data pipes that synchronize blocks of data. Bindings can synchronize arrays, dictionaries, and other complex data types; for example, a drop-down list can be populated automatically by binding it to a data source. Even more usefully, they can synchronize the editing of data, making it possible to add, update, and remove data with a simplified UI and minimal supporting code.

Confusion arises because the Apple documentation implies that bindings require controller objects, specifically NSObjectController, NSArrayController, NSDictionaryController, NSTreeController, and NSUserDefaultsController.

You can use bindings without these objects. You can bind any object to any other as long as both support KVC and KVO. For simple applications, adding a controller adds an unnecessary layer of confusion and complication. Table 13.1 summarizes other common misunderstandings about bindings.

Table 13.1 Facts and Fiction about Bindings

Fiction

Fact

Bindings require a controller object.

You can use bindings without a controller.

Bindings “just work.”

Bindings work as long as you always access values through accessor methods and bind compatible objects to each other. Simple assignments don't trigger the KVO mechanism.

Bindings are always two-way.

Some bindings are read-only. Displayed values may not be editable.

Bindings are designed to work with Interface Builder.

You can create bindings programmatically without using IB.

Bindings eliminate unnecessary code.

Bindings can eliminate pages of code in larger projects. For smaller projects where bindings are used in an outlet-like way, the benefits may be less obvious.

Getting started with bindings

To use bindings successfully, keep these requirements in mind:

bl.eps Bindings work with object properties. You can't bind to a private variable.

bl.eps Bindings rely on KVO. All properties and assignments must be KVO-compliant.

Using accessors

As outlined in Chapter 9, you must use accessor methods when setting the values accessed by bindings.

value = value*10; //This doesn't work

self.value = value*10; //This does - it's obligatory

This is a prime source of confusion. If you don't update values correctly, bindings don't work.

mac_tip.eps Tip

You can bind to system objects as well as to objects in your own code. If their properties are KVO-compliant, bindings will track them as their values change. If they're not KVO-compliant, nothing happens. Class References occasionally mention that properties are KVO-friendly. If they don't, you can test them by trying to set up a KVO observer.

Creating a simple binding

The SimpleBindings1 project on the Web site for this book demonstrates how to bind a counter value to a slider object. You can find the project at www.wiley.com/go/cocoadevref. Load the project before continuing. All the code is in the app delegate. The header defines a counter as a property:

#import <Cocoa/Cocoa.h>

@interface SimpleBindingsAppDelegate : NSObject <NSApplicationDelegate> {

NSWindow *window;

int sliderCount;

}

@property (assign) IBOutlet NSWindow *window;

@property int sliderCount;

@end

The implementation creates a timer, which fires a timerMethod. sliderCount counts to a maximum value, reverses direction, counts down, and reverses direction again at 0.

#import “SimpleBindingsAppDelegate.h”

@implementation SimpleBindingsAppDelegate

@synthesize window, sliderCount;

int delta = 1;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

NSTimer *thisTimer = [NSTimer scheduledTimerWithTimeInterval:0.05 target: self selector: @selector(timerMethod) userInfo: nil repeats: YES];

}

-(void) timerMethod {

self.sliderCount += delta;

if (sliderCount > 100)

delta = -1;

if (sliderCount < 0)

delta = 1;

//NSLog(@”%i”, sliderCount);

}

@end

If you run this code and uncomment the NSLog line and view the console, you'll see sliderCount cycling up and down.

Figure 13.1 shows the project nib file. It's identical to the standard blank template, with an added slider. Note that no outlets or links have been defined for the slider.

Figure 13.1

Adding a slider. You'll use a binding to link the slider to the sliderCount property.

495896-fg1301.eps

To create a binding, select the slider in the Doc window and select the Bindings tab; it's the one with two green objects, third from the right. Figure 13.2 shows how the window appears when no bindings are selected.

Figure 13.2

Displaying the slider's bind-able properties

495896-fg1302.eps

Before you create a binding, take a closer look at this pane. It's displaying a list of some of the slider object's properties. If you review the NSSlider Class Reference, you'll see these items in the properties list. When you bind to one of these properties, you link it to a value in a different object.

Outlets can connect to any property, but bindings only support a subset of object properties. If a property doesn't appear in this list, you can't bind it.

To define the other object, select the slider's value property. Click on the pop-up menu next to the Bind to: check box. You'll see a list of objects in the nib, as shown in Figure 13.3.

Figure 13.3

Displaying a menu of possible objects to bind to

495896-fg1303.eps

Select the Simple Bindings App Delegate, as shown. Select the Bind to: check box next to it. This tells IB that you are binding the slider's Value property to one of the properties of the app delegate.

You select that property by typing its name into the Model Key Path pop-up menu, as shown in Figure 13.4. In this example, there's only one possible property to bind to — sliderCount — so that's what you type here.

There are two possible sources of confusion in this step. The first is that the Model Key Path combo box usually defaults to self, which is meaningless, unhelpful, distracting, and just plain wrong.

The second is that the pop-up menu doesn't show a list of valid properties, even though it could, and should. Bindings would be much less confusing if this feature were available. It would underline the similarities with the outlet-method-linking system elsewhere in IB. Because this feature isn't available, you have to type in the name manually instead of selecting it from a list of possible properties.

Figure 13.4

Selecting a property in the object selected in Figure 13.3

495896-fg1304.eps

Here's a recap of the stages so far:

1. Add an object to the nib.

2. Select the Bindings pane. Pick the property you want to bind to. The list in the Bindings pane is final and non-negotiable; if a property doesn't appear here, you can't bind to it.

3. Choose the object at the other end of the binding from the Bind to: pop-up.

4. Select a property in that object by typing its name into the Model Key Path combo box.

5. Save the nib file, Build and Run.

mac_tip.eps Tip

It's useful to commit this sequence to memory, and then work through it over and over with other examples until it becomes second nature. You'll need this initial level of understanding to use controller objects, described later in this chapter.

Figure 13.5 shows the result. The slider moves from side to side, in an automated way.

Open the console window, and move the slider with the mouse. You'll see that the value of sliderCount automatically updates itself when you release the mouse.

The binding is bidirectional. When the code sets sliderCount, the slider follows it. When the user moves the slider, sliderCount is updated with the new slider value.

In a typical application, sliderCount — or some other property — wouldn't be set by a timer. Bindings are valuable because the timerMethod could be replaced by a mouse tracking method, a disk monitor method reporting free space, or a value pushed from the Internet. As long as there's an accessor assignment, a binding can display it.

mac_tip.eps Tip

See if you can use the delta value in the code, or an associated Boolean property, and the Enable binding to enable the slider while the count is increasing and disable it while it's decreasing.

Figure 13.5

The binding links the slider's value property to sliderCount in Simple Bindings App Delegate. As the count changes, the slider follows it.

495896-fg1305.eps

Working around keypath limitations

The model keypath feature suggests you should be able to access subproperties. For example, the app delegate has a property called someThing of a class with an internal property called aString, as shown in Figure 13.6. You might think someThing.aString would be a valid model keypath.

It isn't. This idiom is valid in some of the various object controllers, but it doesn't work with simple bindings. If you want to bind to subproperties, you have to use KVO to copy a property value to the top-level object after an update, and bind to the copy.

Figure 13.6

Working around keypath limitations. This is an ugly hack, but the model keypath can't usually bind to subproperties directly.

495896-fg1306.eps

Binding incompatible objects

The nib for a slightly modified version of the same project is shown in Figure 13.7. A text field has been added and bound to the sliderCount property, as before.

Figure 13.7

An extended version of the SimpleBindings application, with a text field that can display the slider value numerically.

495896-fg1307.eps

The result is shown in Figure 13.8. The slider continues to move, and the text field displays its value.

This looks like a trivial change, but a lot is happening behind the scenes. The string in the text field is displaying an int value without code. Bindings don't just link two properties; they can also translate values between different data types.

Typically, converting an int to a string requires extra formatting code:

NSString *sliderString =

[NSString stringWithFormat: @”%i”, sliderCount];

Bindings perform a selection of the most useful translations automatically. There's no need for extra code or for explicit format specifications. Figure 13.9 illustrates this graphically.

Figure 13.8

An extended version of the SimpleBindings application, with a text field that can display the slider value numerically

495896-fg1308.tif

Figure 13.9

Don't think of bindings only as property links; they can also interconvert data types.

495896-fg1309.eps

If you open the Console window, type a number into the text box, and press Return, you'll see that this binding is still bidirectional; it sets sliderCount and the slider position. The type conversion is intelligent enough to work in both directions.

Using bindings to manage interactivity

You can use a combination of bindings and KVO to create semi-automated interfaces. Sometimes UI properties depend on each other. For example, selecting one UI feature can disable or enable others, or modifying a value can update its dependent values.

With care it's possible to create UIs with mutual dependence; the user can change any element, and all related elements update themselves automatically. Figure 13.10 shows a very simple example: a two-way temperature converter micro-application. Typing a number into either text field and pressing Return generates an updated value in the other.

Figure 13.10

Tempverter: a very simpletemperature conversion application, implemented with KVO and bindings

495896-fg1310.tif

It's possible to solve this problem with outlets and the text field's delegate methods, using (id) sender to discover which text field was changed, and adding code to update the other. Bindings not only make it easy to manage multiple dependencies, but they also simplify the code. The two temperature values can be floats. Bindings convert floats into text for display, and they convert text into floats for user input. There's no need to add formatting code or supporting intermediate values.

In a more complex application, this can be extended to create multiple propagating dependencies with very little code. But to create a complex UI, you need to understand the key features of a simple one.

The nib file for the project is shown in Figure 13.11. The nib is a standard content view with four text fields. Two are static labels; the other two are used for temperature input and display.

mac_note.eps Note

Figure 13.11 includes a view of the Window Attributes. The window size is fixed by deselecting the Resize control. Close and Minimize have also been disabled. Texture and Shadow effects are applied. These features don't affect the application's operation, but they do improve its appearance.

The header file for the App Delegate is shown below:

#import <Cocoa/Cocoa.h>

@interface TextFieldAppDelegate : NSObject <NSApplicationDelegate>

{

NSWindow *window;

float celsFloat;

float fahrFloat;

}

@property (assign) IBOutlet NSWindow *window;

@property float celsFloat;

@property float fahrFloat;

@end

Figure 13.11

The Tempverter nib file. There are four text fields.

495896-fg1311.eps

Figure 13.12 illustrates how the bindings are assigned. As in the previous example, the bindings select an object — the App Delegate — and the Model Key Path names a property in that object.

This is enough to create a two-way link between the text field and celsFloat. If the user types a number into the Celsius text field and presses Return, it's converted into a float value and is copied to celsFloat. If the application writes a new value to celsFloat, it's converted into a string and copied to the text field.

Adding a corresponding binding between the Fahrenheit text field and fahrFloat is almost enough to create a simple application. There are still two features missing: the temperature conversion code and KVO management to trigger dependent updates.

mac_note.eps Note

Internally, the bindings use the stringFromFloat: and floatValue methods built into NSString. Text is interpreted as an error and produces a float value of 0.

Figure 13.12

Binding the Celsius text field to the corresponding float in the App Delegate

495896-fg1312.eps

Using KVO to manage bindings

Converting one number into another is almost easy. You add a KVO observer to both floats. When the user types in a number, the binding updates the corresponding float. This triggers the KVO observer method. You can do some simple arithmetic in the observer, and then write the converted value to the other float … which triggers its KVO observer, which does some arithmetic on the other value … and loses itself in an infinite loop.

It would be possible to enable and remove KVO selectively, ignoring each value as it's being updated. In this example you've created a blanket method that turns off KVO for both floats whenever they're being updated, and re-enables it when the update has completed.

A simple KVO switch would be useful, but Cocoa doesn't have one. KVO can only be enabled and disabled separately for each possible key. This puts some practical limits on KVO, which can't usually observe more than a few tens of properties. Trying to observe more results in code can be difficult to debug and can also create a significant performance hit, because the technology used to implement KVO isn't fast or efficient.

mac_note.eps Note

Behind the scenes, KVO works by using a trick called isa swizzling — a fancy way of saying that classes are copied and modified on the fly, and updated versions impersonate the originals. This isn't a speedy process.

So in this example, you explicitly enable and disable KVO for all properties with a method called isObserving: in the App Delegate. The method is listed below:

#import “TempverterAppDelegate.h”

@implementation TempverterAppDelegate

@synthesize window, celsFloat, fahrFloat;

- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {

self.celsFloat = 100.0;

self.fahrFloat = 212.0;

[self isObserving: YES];

}

- (void) isObserving: (BOOL) observing {

if (observing) {

[self addObserver:self forKeyPath:@”celsFloat” options:NSKeyValueObservingOptionNew context:NULL];

[self addObserver:self forKeyPath:@”fahrFloat” options:NSKeyValueObservingOptionNew context:NULL];

} else {

[self removeObserver: self forKeyPath:@”celsFloat”];

[self removeObserver: self forKeyPath:@”fahrFloat”];

}

}

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change

context:(void *)context {

NSLog(@”%Keypath:%@ Value: %@”, keyPath,

[self valueForKey:keyPath]);

[self isObserving: NO];

if ([keyPath isEqual: @”celsFloat”])

self.fahrFloat = 32+celsFloat*1.8;

if ([keyPath isEqual: @”fahrFloat”])

self.celsFloat = (fahrFloat-32)/1.8;

[self isObserving: YES];

}

@end

Key features of the code include:

bl.eps applicationDidFinishLaunching: sets initial defaults for both temperatures and then turns on KVO.

bl.eps The isObserving: method takes a BOOL and adds and removes observers for the two temperatures dynamically.

bl.eps The standard KVO observeValueForKeyPath: method is triggered when either float changes — but only if KVO is enabled. It logs the trigger event, turns off KVO to prevent a loop, updates one of the floats, running code chosen by testing the keyPath, and turns KVO on again after the update.

It's important to understand that bindings and KVO act independently. You can turn off KVO without affecting bindings, because the bindings manager is a separate object. Behind the scenes, out of sight of your code, it's running its own KVO monitoring. It doesn't care how you use KVO elsewhere.

mac_note.eps Note

Don't forget to prefix properties with self to make them visible to KVO and bindings.

Using formatters

The disadvantage of the automatic conversion in the bindings manager is that it doesn't support format control. If you type values into the Fahrenheit box, you'll see that the equivalent Celsius value is often a recurring decimal. The text field truncates this visually. A wider text field would show them.

NSNumberFormatter is a simple drop-in formatting object that can solve this problem. It's not directly related to bindings, but it's often used to fine-tune the format of the strings produced by the bindings manager. Figure 13.13 illustrates how to add an NSNumberFormatter to a text field.

mac_caution.eps Caution

The formatter works on a text cell, so you must drop it on the NSTextFieldCell in the nib, not on the text field itself.

Figure 13.13

Adding a number formatter to a text field to tidy up the string representation of the bound float.

495896-fg1313.eps

NSNumberFormatter has many options and can perform unusual tricks. For example, it can convert numbers into text strings such as “ten” or “fifty three,” with associated local language support. In this example, you'll use the settings shown in Figure 13.14 to limit the text field to two decimal places. The two Format text boxes near the top of the Number Formatter Attributes pane set the format, and the number of hash signs after the decimal point sets the number of decimal places. You must select the Mac OS X 10.4+ Custom Behavior at the top of the pane to see the Format options.

To complete the application, add another formatter with the same settings to the Fahrenheit text field.

Figure 13.14

Setting two decimal places in the Format boxes. You can use a formatter to define scientific and financial formats, with optional special characters, including currency signs.

495896-fg1314.eps

mac_tip.eps Tip

You can use the associated NSDateFormatter objects to control the format of date values.

Using Bindings with Controllers

Controller objects — NSObjectController, NSArrayController, NSDictionaryController, NSTreeController, and NSUserDefaultsController — add an extra level of sophistication to bindings, and four or five extra layers of complexity. Controllers become useful when bindings are used as data pipes. In the same way that simple bindings can connect different properties without formatting or conversion code, controllers can connect complex objects and translate data between them.

You can use controllers to produce complex applications with very little code. For example, NSTableView is the standard Cocoa object used to display and manage tables. It's a complex class with many features and delegate methods. To implement a TableView without bindings, you must write glue code — code that connects two objects or features together and translates data between them. For example, an array can be translated into a list of values. The code is embedded in delegate methods that are called when the table needs to display, refresh, or modify data. It can easily become very complex.

Controller objects can minimize the glue code. In some applications, they can eliminate it completely. To demonstrate, the example below demonstrates how you can create an application that displays a list of running processes in OS X — with exactly one line of active code.

The App Delegate header file looks like this:

#import <Cocoa/Cocoa.h>

@interface TableBindingAppDelegate : NSObject <NSApplicationDelegate> {

NSArray *runningApps;

NSWindow *window;

}

@property (assign) IBOutlet NSWindow *window;

@property (nonatomic, retain) NSArray *runningApps;

@end

It's a copy of the standard template, with an added NSArray property called runningApps.

The App Delegate implementation looks like this:

#import “TableBindingAppDelegate.h”

@implementation TableBindingAppDelegate

@synthesize window, runningApps;

- (void)applicationDidFinishLaunching:

(NSNotification *)aNotification

{

self.runningApps =

[[NSWorkspace sharedWorkspace] launchedApplications];

NSLog(@”%@”, runningApps);

}

@end

This code doesn't do much. It calls the NSWorkspace object and asks for a list of running applications, which is copied to the array. An NSLog call lists the running apps to the console. The logging is optional, but it's very useful, as you'll see next.

Figure 13.15 shows the finished application. It has the following features:

bl.eps There are three independent columns of data showing values from the runningApps array.

bl.eps The table dividers can be moved.

bl.eps Rows can be selected by clicking on them, and there is an optional selection index used to return information about the number of the current selection.

bl.eps Columns can be sorted by clicking on each column header to select ascending or descending sorting.

The application has some limitations. It doesn't auto-refresh the table; the array is loaded once at launch, and isn't updated. In its basic state, it's a display-only app. There are no features for starting and stopping apps, saving the list to a file, maintaining a log file, and so on.

But it's doing a lot with very little code. And most of the intelligence is built into a controller object, coupled to the array and the table view via bindings.

Figure 13.15

Filling a table view with bindings. Most of the active code is built into a controller object and runs automatically.

495896-fg1315.tif

Adding a controller object

Figure 13.16 shows the project nib file. The table view automatically adds a surrounding scroll view. The default table view has two columns. Another column has been added by copying and pasting it. Each column's name — the text string that appears in the divider, at the top of the header — is set in the Attributes pane in Inspector. The Size pane sets the default width and optional width limits. Take some time to explore these features before continuing.

Figure 13.16 shows the list of controller objects in the Library window. In this example, you're using an array as a data source, so you add an instance of NSArrayController to the project by dragging it from the window.

Remember that adding an object to a nib automatically generates an instance in memory when the nib loads. You don't need to alloc/init the controller in your code — it's already loaded from the nib.

Optionally, you could subclass it to access its internal features, adding a supporting subclass — perhaps called ArrayControllerSubclass — and using IB's identity tab to reclass the default controller as your subclass. You don't need to do that here. Instead, you'll discover how to use the new controller.

mac_tip.eps Tip

When you add a column, its default width may be so wide you can't see it, because it has fallen off the right-hand edge of the table. Setting the widths manually can recover it. Once a column is visible, you can set its width by dragging it with the mouse. This is a tricky process: you'll need to click on the header tab between two and four times, depending on the table's selection state.

Figure 13.16

Filling a table view with bindings. Most of the active code is built into a controller object and runs automatically.

495896-fg1316.eps

Setting up the controller's data source

Controllers act as a bridge object between a source of data and a view object. To use a controller, you bind it twice. The first binding selects a data source for the controller. It defines the controller input, although, unfortunately, Apple doesn't call it that — controllers would be easier to understand if this relationship were clearer.

The second binding links a view to the processed data generated by the controller — in other words it displays the output, although again Apple's naming scheme doesn't make this obvious.

Defining a controller data source is as easy as creating any other binding. You select a source object, choose a property in that object, and type the property name into the Model Key Path combo box.

A key source of confusion is the cryptic nature of controller properties. Controllers are general-purpose objects, designed to work with a variety of data sources. The controller doesn't care if your array holds application statistics, ages, salaries, or a list of probabilities of Earth impact for every major asteroid.

Instead, the controller properties generalize the data connection. So NSArrayController has a property called Controller Content. This is a general wrapper object that holds the same content as the data source. In the same way that File's Owner is a placeholder for the object that created a nib, think of Controller Content as a placeholder for your data source. When you bind to the controller to display data from it, it becomes the data source. It's then translated, through binding magic, into a format that a compatible view object can display without code. Figure 13.17 shows how this works.

mac_caution.eps Caution

Controller Content is a property of the NSController superclass. All the controller objects support it. But in an array controller, the content is an array. In a dictionary controller, it's a dictionary, and so on.

Figure 13.17

Using a controller object as a two-way binding. The Controller Content property is a placeholder for data from the source object.

495896-fg1317.eps

Now that you've been introduced to the Controller Content property, it's easy to see how to connect the array data to the controller. Selecting the Controller Content tab, you bind it to your data source: the runningApps array in the App Delegate, as shown in Figure 13.18.

Figure 13.18

Binding to the Controller Content property of the controller, so that it uses the runningApps array as a data source

495896-fg1318.eps

Reading data from the controller into a view

Reading translated data from the controller is more difficult. Instead of following through with the Controller Content metaphor, this side of the process uses different properties. Effectively, you're going to look inside the Controller Content object and pull out various slices through the data.

The first — critical — point is that it's only possible to pull data out of the array if its objects support key-value property access. Figure 13.19 shows a log of the contents of the runningApps array. Usefully, this array is a collection of dictionary objects, one for each running app. It's easy to pick out the relevant keys from the log; for example, NSApplicationName is the application name. However, not all objects support this kind of access. If they don't, you can't use bindings to display their data.

Figure 13.19

Understanding the format of the data in the runningApps array. Every item is a dictionary.

495896-fg1319.tif

When using bindings, this point is crucial. You must be able specify a keypath for each displayed property, and it must return a useful value. If you want to use bindings with custom objects, you must design them in a way that supports keypath value access. Dictionaries are ideal for this because they explicitly implement key-value access.

You can't, however, access a simple linear array of strings, because the strings won't have an associated key. An array index isn't enough to support bindings, and bindings don't support index access. You can't use someArray.0 as a keypath.

This complicates the design of applications, because typically you need to pack array data into a dictionary before it can be accessed. Other workarounds are possible, but wrapping content into a dictionary is often the simplest solution.

In this example, dictionaries are already available. So you can move on to the second part of the problem: selecting the formatted data. Figure 13.20 shows the Controller Key combo box. This is another element that seems mysterious, but it is quite simple in practice.

Figure 13.20

Listing the possible Controller Key options to select how you want to view the data in the source array

495896-fg1320.eps

Understanding controller keys

In the same way that the controller abstracts data from the data source, it also abstracts its display. Each item in this combo box is one possible interpretation or view of the data copied to the controller. Again, these items aren't related to specific named properties in the original data, and the controller takes no interest in what the data represents or how it's named.

Instead, these Controller Keys select one particular summary or element of the data. Table 13.2 describes the more useful items in this list.

A key feature of all controller keys is that the data selected by these keys is determined by the user's actions. The datasource array doesn't change, but the view of the data is modified as the user interacts with the table view or other display object. Figure 13.21 illustrates the process graphically.

Table 13.2 Some common Controller Keys

Name

Description

arrangedObjects

An array holding a sorted copy of the source data. When the user changes the sort options in the table view, this array is re-sorted and updated automatically.

Selection

A copy of the current selected object, updated when the user selects an object. (Use keypaths to extract values from it.)

selectionIndexes

An NSIndexSet of currently selected objects, if there are multiple selections.

selectionIndex

An NSUInteger index of the currently selected object. (Invalid for multiple selections.)

selectedObjects

An array of the objects that are currently selected.

Figure 13.21

When binding to visible objects in the UI, the controller can display various interpretations of the data, including selected elements chosen by the user.

495896-fg1321.eps

mac_tip.eps Tip

Lower down the list, you'll see keys like canInsert. You can use these to enable and disable editing features. Creating an editable table is more of a challenge, and some extra code is needed. Typically you'll subclass the data source array, the controller, or both to implement full editing.

Selecting controller keys

Now that you know what controller keys do, it's easy to select the one you need to bind a table column to a slice of data from the runningApps array. Figure 13.22 illustrates how to do this. You select arrangedObjects to display a sorted list of apps in the array, and then choose the NSApplicationName key to retrieve the name string of each app.

Figure 13.22

Selecting a Controller Key and an associated Model Key Path to copy values from the data source array into a table view

495896-fg1322.eps

When the application runs, the binding manager automatically accesses each running application in turn, pulls out the value associated with the NSApplicationName key, and adds it to the table column, creating the list shown previously in the left-hand column in Figure 13.15.

You can repeat this for other properties in the other two columns. In this example, the middle column displays the NSApplicationProcessIdentifier key, and the rightmost column displays the NSApplicationPath key.

Here's a review of the steps that created the finished application:

1. You added code to initialize a data source with data.

2. You added a controller object to the nib.

3. You bound the controller object to the data source, copying data to its Controller Content property.

4. You added view objects to the nib.

5. You bound the view objects to view content generated by the controller, selecting different possible view content with the Controller Keys.

6. Finally, you selected the values that appear by typing a property name into the Model Key Path.

mac_tip.eps Tip

You can find two versions of this project on the Web site for this book at www.wiley.com/go/cocoadevref. A slightly extended version adds an extra text field that you can experiment with. Try binding selection, selectionIndex, and some of the Boolean values to the text box to see how they change as you select items in the table.

Implementing Preferences with Bindings

Among the controller objects used with bindings is NSUserDefaultsController. This is a special object that takes its data from the application's preferences. The controller is shared. You can create multiple instances of it in multiple nibs, and every instance accesses the same data. You don't need to bind it to a data source, because it's already bound to the preferences system.

Bindings can simplify preferences in two ways:

bl.eps You can bind objects and views in a preferences pane directly to the NSUserDefaultsController. The controller will save and load the application preferences automatically, so this pane will always be correct.

bl.eps You can bind objects in the main application nib to NSUserDefaultsController to display them, or a modified version of them, in a different location. For example, you may want to include a hint or reminder item in a toolbar that shows the current value of a preference.

A full preferences implementation should read settings from the code version of NSUserDefaultsController, which is called NSUserDefaults, and is used like this:

NSUserDefaults *thePrefs = [NSUserDefaults standardUserDefaults];

You can then use key-value access to read the defaults. More creatively, you can also use Key-Value Observing (KVO) to monitor changes to preferences, updating selected settings immediately. Poor implementations of preferences force the user to close and re-open an application before changes are registered by the rest of the application. A more professional implementation can use KVO and bindings to respond to changes as they happen.

mac_caution.eps Caution

If you're using bindings and a separate preferences window, it's best not to set the defaults by setting the values in NSUserDefaults with code. Limit changes to the preferences UI and its bindings, and use code elsewhere in the application to read values from NSUserDefaults, or track changes to them with KVO.

Understanding preferences

The Cocoa preferences system is unusual. Preference keys and values must be registered before they can be used. Internally, the preferences system keeps a copy of the original registered values and only saves changes to them.

Initializing preferences

To register keys, run an initialize method inside a preferences method or in your application delegate. initialize — not to be confused with init — is the very first method run by every class as the application loads. You can use it to run start-up code that initializes critical values. A typical preferences method that uses NSUserDefaultsController looks like this:

+ (void) initialize {

NSMutableDictionary *prefsDictionary =

[NSMutableDictionary dictionary];

[prefsDictionary setObject: <an object> for Key: <a key];

[[NSUserDefaultsController sharedUserDefaultsController setInitialValues: prefsDictionary]];

}

Add one setObject: line for every item in the preferences file.

mac_note.eps Note

initialize is a class method.

Including this code defines the names and keys used in the preferences data. You can read and write the keys elsewhere in your application.

Because preferences are objects, you must “objectify” them. Numbers must be packed into

[NSNumber numberWith<Type>: value];

Generic objects must be archived into an NSData object. For example, to include a color use

[NSData *thisColor =

[NSArchiver archivedDataWithRootObject: [NSColor whiteColor]];

[prefsDictionary setObject: thisColor forKey: <a key>];

Setting preference keys

It can be useful to define global, application-wide, preference key constants. Literal strings won't trigger a compiler error if you type them incorrectly. Explicitly defined constants will. This is an optional extra feature, but it can help make an application more robust.

extern NSString * const AKey = @”A key name”;

to define the key constant.

You can then use AKey as a substitute for @”A key name” throughout your application.

Reading preferences values

To read a preferences value in your application, access the preferences dictionary with a standard valueForKey: method. It's useful to initialize a pointer to the preferences dictionary once at the start of the application and then refer to it thereafter.

NSUserDefaults *prefs;

prefs = [NSUserDefaults standardUserDefaults];

<type> aPref = [prefs valueForKey: <a key>];

When you read an archived or “objectified” key to check its value in your application, you must reverse the archiving or objectification process.

float aFloat =

[[prefs valueForKey: <a key which is a float>] floatValue];

NSColor *thePrefColor = [NSUnarchiver unarchiveObjectWithData: [prefs valueForKey: <a key>];

You can eliminate some of the conversions by binding variables to objects that display or control a preferences setting. For example, you can bind a slider directly to an NSNumber value in the preferences without converting it.

Creating an application with preferences

Figure 13.23 shows a simple application with a preferences panel. KVO and bindings are used to monitor changes to the panel and refresh the main application window.

Figure 13.23

When binding to visible objects in the UI, the controller can display various interpretations of the data, including selected elements chosen by the user.

495896-fg1323.eps

The code for the Application Delegate is:

#import “PreferencesAppDelegate.h”

@implementation PreferencesAppDelegate

@synthesize window, preferencesController, theColorField;

NSUserDefaults *prefs;

- (void)applicationDidFinishLaunching:(

NSNotification *)aNotification {

prefs = [NSUserDefaults standardUserDefaults];

//Using KVO on the prefs values, we can trigger updates when the

//prefs change

[prefs addObserver:self forKeyPath:CDRFavColorKey options:NSKeyValueObservingOptionNew context:NULL];

[prefs addObserver:self forKeyPath:CDRCommMediumKey options: NSKeyValueObservingOptionNew context:NULL];

[prefs addObserver:self forKeyPath:CDRDegOfAwesomeKey options:NSKeyValueObservingOptionNew context:NULL];

//Reload color from prefs on startup

[self updateColor];

//Automatically show the prefs window on load

[self showPreferences:nil]; }

- (void) showPreferences: (id) sender {

//Create a new preferences controller object

NSLog(@”Showing preferences”);

if (!preferencesController)

preferencesController = [[PreferencesController alloc] init];

[preferencesController showWindow: self];

}

-(void) updateColor {

//Get the curent color from the prefs and apply it

NSColor *favoriteColor = [NSUnarchiver unarchiveObjectWithData: [prefs valueForKey:CDRFavColorKey]];

theColorField.backgroundColor = favoriteColor;

}

- (void) observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object

change:(NSDictionary *)change

context:(void *)context {

if (object == prefs) {

NSLog(@”%Keypath:%@ Value: %@”, keyPath, [object valueForKey:keyPath]);

if ([keyPath isEqual: CDRFavColorKey])

[self updateColor];

}

}

@end

Key features of the application include:

bl.eps The Preferences item in the application menu triggers a showPreferences: method in the App Delegate.

bl.eps The method allocates an instance of a custom Preferences Controller object.

bl.eps When the Preferences Controller is created, it uses a custom init method to load and display the contents of a separate Preferences Panel nib file. The nib, shown in Figure 13.23, includes some UI elements and an instance of NSUserDefaultsController. The UI elements are bound to the controller. The code initializes the preferences. UI updates, saving, and loading are handled automatically by the controller. No other code is needed.

bl.eps The main application window includes a couple of UI elements that are bound to another copy of NSUserDefaultsController. (Because this is a shared object, every copy accesses the same data.)

bl.eps Color data can't be bound, so color updates are monitored with KVO. Observers are initialized for every preferences key. The common observer method checks the key and runs update code to change the background color of the main text view when the color preference is updated.

bl.eps In a more complex application, code here could add further auto-refresh features.

bl.eps One of the bound fields uses a value transformer, described below.

mac_note.eps Note

You can add an empty nib file to any project by right-clicking the Resources group in Xcode, choosing Add New File, selecting the User Interface tab and Empty XIB, and then saving and naming the file in the usual way.

Figure 13.24 shows the preferences panel nib. Instead of an NSWindow, the panel uses an instance of NSPanel to create a miniwindow suitable for preferences and other lightweight display tasks. Otherwise, it's a standard nib with a content view, some controls, and the Shared Defaults controller.

To bind to values in NSUserDefaultsController, use the values Controller Key. In the same way that NSArrayController offers arrangedObjects, selection, selectionIndex, and other predefined keys, NSUserDefaultsController offers the simpler values controller. As the name suggests, this is a simple list of its values. To access a value, type in its key name in the Model Key Path.

mac_tip.eps Tip

NSUserDefaultsController is added to a nib automatically when you bind to it as a data source. You don't need to add it by hand.

Figure 13.24

Binding preferences objects. Selecting the Shared User Defaults Controller as the source makes it possible to access values in the application's preferences.

495896-fg1324.eps

Creating and Using Value Transformers

The preferences panel for the application includes a radio button, whose setting is saved as a numerical index. It's possible to convert the index to a string in the application using a switch statement, but it would be useful to create a binding that displays the correct string automatically.

Value transformers are a customizable extension to the transformation code built into bindings. You can use them to convert a value from any object into any other object, or to process values in more complex ways, such as performing math, changing data formats, or even sending values over the Internet to a remote server.

A value transformer is a subclass of the NSValueTransformer class. To create and use a transformer, follow these steps:

1. Create a new object in Xcode. Replace the header and the start of the implementation with the value transformer boilerplate code provided below.

2. Add more boilerplate code to the App Delegate's initialize method to register the transformer.

3. Type the transformer's name into the Value Transformer box under the Model Key Path.

When you save the nib file and build and run the application, the transformer processes the value pulled from the data source and replaces it with a transformed value.

In this example, you'll create a transformer called IndexToNameTransformer that takes a radio button index and returns a corresponding string. The boilerplate to register the transformer is shown below. Add equivalent code to the App Delegate, replacing the name field with the name of your own transformer object. You can allocate multiple transformers. Each must have a unique name string.

+ (void) initialize {

NSValueTransformer *transformer =

[[IndexToNameTransformer alloc] init];

[NSValueTransformer setValueTransformer:transformer forName:@”IndexToNameTransformer”];

}

The transformer object header declares your custom subclass of NSValueTransformer, which defines the transformer's name.

#import <Cocoa/Cocoa.h>

#import <Foundation/Foundation.h>

@interface IndexToNameTransformer : NSValueTransformer

{}

@end

Properties and other variables are declared in the implementation, listed here. A switch statement converts the incoming int into a text string, which it returns.

#import “IndexToNameTransformer.h”

@implementation IndexToNameTransformer

//These two methods are boilerplate

+ (Class)transformedValueClass

{

return [NSString class];

}

+ (BOOL)allowsReverseTransformation

{

return NO;

}

//The transformer method signature is fixed, the code is customizable

- (id)transformedValue:(id)aValue

{

int thisIndex = [aValue intValue];

switch (thisIndex) {

case 1:

return @”Radio”;

break;

case 2:

return @”TV”;

break;

case 3:

return @”Internet”;

break;

case 4:

return @”Pigeon (RFC 1149)”;

break;

default:

return @”Paper”;

break;

}

}

@end

Figure 13.25 illustrates how to use the value transformer. Type the name manually into the Value Transformer combo box under the Model Key Path. The result was shown in Figure 13.23: the index was automatically converted into an associated string, which appeared in a text box. Without the transformer, binding to the radio button preference would have displayed a number.

mac_tip.eps Tip

The bindings manager includes a selection of predefined transformers. The two most useful are NSUnarchiveFromDataTransformerName and NSKeyedUnarchiveFromDataTransformerName, which can convert archived preferences objects back into objects. For an example, see the Favorite Color binding in the preferences application.

Figure 13.25

Setting a value transformer. Once the transformer is registered in code, you type the name into the Value Transformer box to apply it to the data source selected in the Model Key Path.

495896-fg1325.eps

Summary

In this chapter, you learned how to use bindings. You began with a look at why bindings are useful, and then explored some of their less well-known features. Next you looked at a practical example of creating and using simple bindings that connected a UI control to a value set inside an application, in both directions.

To show you how to create more sophisticated applications, you were introduced to controller objects, and you learned about their features and functions and how they can be used to eliminate glue code.

You discovered how to use bindings and KVO to create a complete preferences solution for applications, with some sophisticated features. Finally, you learned about value transformers, and you saw a simple sketch of a transformer class that you can customize and use in your own projects.