Cocoa Bindings Examples and Hints


These examples are provided on an as-is basis. Use at your own risk. They're intended to illustrate different aspects of the controller technology, so are not necessarily consistent (for example some don't use appropriate accessor methods). Please concentrate on the main point of focus, not on the various deficiencies. If you find anything particularly egregious, however, please let me know. Comments and feedback please to (controllers at mmalc dot com). Many thanks in particular to Scott Anguish for his help with code and concepts, and to Scott Stevenson for the CSS.

For an overview of what Cocoa bindings is, see Bindings in a Nutshell.

Updates

Date Details
December 28, 2007 Added SearchFieldBindings example.
November 8, 2007 Updated comments about unbinding .
November 1, 2007 Added MigratingDepartmentAndEmployees. This example uses a modified version of the Department and Employees example from the NSPersistentDocument Core Data Tutorial to illustrate how to use the Core Data versioning and migration API introduced in Mac OS X v10.5. And it includes a binding.
October 30, 2007 Updated ToDos example to use Objective-C 2.0, added an extra value transformer.
Made minor change to BindingsDocumentInspector example to allow ESC to cancel edits.
October 28, 2007 Added BindingsDocumentInspector example.
Updated GraphicsBindings and ToManyCheckbox examples to use Objective-C 2.0.
October 24, 2007 Updated Bookmarks example to provide implementation of countOfIndexesInRange:.
October 23, 2007 Updated Graphics Bindings example to remove unnecessary implementation of exposedBindings[1].
September 22, 2007 Updated Bookmarks example to use more efficient and more obvious algorithm in moveObjectsInArrangedObjectsFromIndexes:toIndex:.
September 18, 2007 Added link to preliminary ToManyCheckbox example.
August 5, 2007 Added back link to Bookmarks example; updated example to allow copying within a table view.
July 5, 2007 Added notes about binding across nib files and missing KVO old and new values.
June 17, 2007 Added note about Observing a collection.
June 13, 2007 Redirected link from "Bookmarks" example to "With and Without Bindings"; added link to "Bound Button" example.
June 8, 2007 Added link to the samples for the WWDC07 "Getting Started with Cocoa Bindings" session.
June 7, 2007

Updated Combatants to set up bindings in Interface Builder and corrected binding bug (original bound the array controllers' content instead of contentArray).

Updated link to Apple source for Department and Employees example.

May 18, 2007 Corrected minor memory leak (oldGraphics) in Graphics Bindings[2].
May 7, 2007 Updated Graphics Bindings example to use infoForBinding: and to unbind in viewWillMoveToSuperview: (see Unbinding).
March 4, 2006 Updated Joystick in Graphics Bindings example to support arrow keys.
February 10, 2006 Added link to Department and Employees example.
February 7, 2006 Update to sections on retain cycles to note fix in 10.4 and on subclassing an array controller to specify controller keys. Added link to ClockControl example.
July 8, 2005 Update to FilteringController to remove spurious array controller outlet in MyDocument.nib[3]. Upgraded to Xcode 2.1.
July 2, 2005 Update to notes on using arrays with NSUserDefaultsController to reflect update to Uli Zappe's example.
May 7, 2005 Update to Controlled Preferences example: View uses KVO to observe user defaults.[4]
May 7, 2005 Update to Manual Bindings example: Uses NSValidatesImmediatelyBindingOption for Mac OS X 10.4 and later.[3]
January 26, 2005 New note regarding bindings for views in an IB palette.
January 14, 2005 Updated section on Memory Management and Retain Cycles.
December 21, 2004 Included this revision history.
December 21, 2004 Updated NSUserDefaultsController section: Issues with collections, reference to Uli Zappe's example.

[1] Thanks to Jerry Krinock. [2] Thanks to Sean McBride. [3] Thanks to Bill Cheeseman. [4] Thanks to Bill Modesitt.

Guide to the examples

Example Document-based? Programmatic bindings? Custom controller? Other issues
Simple Bindings Adoption Yes Yes No

An example that illustrates adoption of Cocoa bindings. There are three implementations of a very simple document-based application:

  1. The user interface is updated programmatically using target/action.
  2. The user interface is updated using bindings; bindings are established programmatically.
  3. The user interface is updated using bindings; bindings are established in Interface Builder.
With and Without Bindings Yes No Yes

Shows a custom array controller that implements table view data source methods to support drag and drop, including copying objects from one window to another, drop of URLs, and re-ordering of the content array.

This example contains two implementations: the first uses standard NSTableView data source methods, the second uses bindings.

Bookmarks Yes No Yes

This is the original version of "With and Without Bindings", and is slightly more advanced. It shows a custom array controller that implements table view data source methods to support drag and drop, including copying objects from one window to another, drop of URLs, and re-ordering of the content array.

This example only contains a bindings implementation, but supports multiple items in a drag, whereas the "With and Without Bindings" example does not; it also allows copying as well as moving within a table.

ToManyCheckbox Yes No No Core Data-based example to illustrate a user interface that allows you to manuipulate a to-many relationship using a checkbox in a table column to select items. Note: This example uses Objective-C 2.0. This is a work in progress. Please report any bugs to controllers @ mmalc.com.
BindingsDocumentInspector Yes No No This example illustrates how to create an inspector using Cocoa bindings. Note: This example uses Objective-C 2.0. This is a work in progress. Please report any bugs to controllers @ mmalc.com.
Clock Control 2a No No No An update to the Clock Control example that ships with the developer tools. This adds bindings support to the control, shows integration with an Interface Builder palette, and allows the control to be placed in a table view. Note that this is a work in progress. Please report any bugs to controllers @ mmalc.com.
Bound Button No No No Illustrates binding a button's target and action parameters (and explains that this is not something you're likely to want to do frequently).
Combatants No Yes Yes Shows interaction between two controllers so one table view displays the same list as another, but with the selection in the other removed. Custom classes implement Combatant and Weapon; a combatant has three weapons stored as individual instance variables, indexed accessors are used to represent the weapons as an array
Controlled Preferences No Yes No Shows use of NSUserDefaultsController. It also illustrates the use of NSFontPanel, and a value transformer to retrieve the display name of a font.
Filtering Controller Yes No Yes Shows custom array controller that interacts with a search field (NSSearchField) to filter the displayed content of a table view.
SearchFieldBindings No No No Shows how to configure and use a search field using code and using Cocoa bindings.
Graphics Bindings Yes Yes Yes By far the most complicated example, shows the use of a custom controller, a value transformer, and two custom bindings-enabled views. One view is a control that allows you to set the angle and offset of a shadow; the other view observes and displays a collection of graphic objects.
Manual Bindings No Yes No A simple example that illustrates establishing bindings programmatically, including a number of options such as validation and an array operator, and indexed accessor methods. A custom model object implements custom validation method.
To Dos Yes No No Shows two array controllers, one to manage the contents of a table view, the other to manage a pop-up menu in a table column. Also shows two value transformers to alter the color of text, and a generic value transformer to create styled string representations of dates.
Department and Employees Yes No No This is the source code to the Department and Employees example given in NSPersistentDocument Core Data Tutorial. It also includes an example of using a custom sheet and peer managed object context to create a new Employee object.

The ubiquitous tableview

Many of the controller/bindings examples use tableviews, which has led to some believing that this technology is not useful if you're not displaying a list of objects. This is certainly not the case. The reasons for choosing the tableview are partly pragmatic and partly "economic". Firstly, tableviews have been traditionally fiddly to deal with. Controllers remove a lot of the tedious code you had to write. More than that, though, tableviews also require an understanding of several important Cocoa design patterns, including delegation (albeit indirectly through the datasource model). From that perspective they're a useful learning tool, since they help to exemplify techniques that will apply elsewhere. Finally, particularly in the context of controllers and these examples, they're a ready-made view. Many of the same techniques that apply to managing a collection of employees displayed in a tableview will apply to managing a collection of graphic objects displayed in a custom view. The latter example, though, requires a significant amount of extra code that is not directly relevant to the task of illustrating how controllers work.


What is the difference between a "binding the value" and "setting it directly"?

In brief: They both set the same property on the bound object, but a binding is dynamic. In more detail:

Per Cocoa Bindings Programming TopicsCocoa Bindings Programming Topics > What Are Cocoa Bindings?:

What Are Cocoa Bindings?
In the simplest functional sense, the Cocoa bindings technology provides a means of keeping model and view values synchronized without you having to write a lot of “glue code.” It allows you to establish a mediated connection between a view and a piece of data, “binding” them such that a change in one is reflected in the other.
[...]
What Is a Binding?
A binding is an attribute of one object that may be bound to a property in another such that a change in either one is reflected in the other.


If you set the contentObject of an object controller, then -- just as anywhere else in Cocoa -- you're setting one of its properties (typically an instance variable) and it's then the recipient's responsibility to look after it. If you want to change the content object (that is, provide a different object rather than simply modifying properties of the object itself) you have to set the contentObject again (glue code).

If you bind the contentObject, they you're telling it to keep it synchronised with whatever is at the end of the keypath you provided from the source object. So if you have a document with an attribute (typically an instance variable with suitable accessor methods) mainObject and you bind an object controller's contentObject to [Document].mainObject, then if you set (in a KVO-compliant way) a new object for mainObject, the controller's content will be automatically updated to be this new object (contrast the non-bindings situation where you would have to send a setContent: message yourself).

In many simple cases there may be little reason to bind the content rather than simple set it directly, except that it's possible to establish the binding in IB without any code -- whereas if you wanted to set it directly you'd have to write code.


Unbinding

Early versions of the Joystick and GraphicsBindings examples, and Apple's sample code and documentation (for example, How Do Bindings Work? > The Supporting Technologies in Detail > Unbinding) suggest views unbinding in dealloc. To support garbage collection on Mac OS X v10.5, if your view will not become full-screen it is now recommended that this be done in viewWillMoveToSuperview:, for example:

- (void)viewWillMoveToSuperview:(NSView *)newSuperview
{
	[super viewWillMoveToSuperview:newSuperview];
	if (newSuperview == nil)
	{
		[self unbind:ANGLE_BINDING_NAME];
		[self unbind:OFFSET_BINDING_NAME];
	}
}

If your view may become full screen, then you should ideally use another invalidation method to mark when the view is finished with and unbind in that, otherwise you should unbind in finalize (and/or dealloc if you're supporting mixed mode/using managed memory).


Programmatic modifications to arrays not noticed by table view

In some situations you may find that programmatic modifications to arrays pass unnoticed by a table view. You should not modify the contents of an object "behind the controller's back" (unless you want to make a large number of changes in a single operation, and handle the observer notification yourself -- see below ("Batch modification of collections").

You can safely modify a bound-to to-many relationship by:

KVO-compliant accessor methods for to-many relationships are detailed here: http://developer.apple.com/documentation/Cocoa/Conceptual/KeyValueCoding/Concepts/AccessorConventions.html. The general form of the methods is summarised below:


Observing a collection is not the same as observing the properties of the objects in a collection

Suppose you have a class that defines an instance variable name, and you have an array of these objects managed by an array controller. If you observe the array controller's arrangedObjects as follows:

[arrayController addObserver:self forKeyPath:@"arrangedObjects" ...];

then:

  1. You will receive KVO change notifications if objects are added to or removed from the array;
  2. You will not receive KVO change notifications if the name of any of the objects is changed.

For an example of how to register to receive change notifications for the properties, see Graphics Bindings. To summarise: You need to register as an observer of the relevant properties of each of the objects in the array, and as an observer of the array itself so you can add yourself as an observer when a new object is added to the array and remove yourself when an object is removed from the array.

If you want to observe multiple properties but react to changes in any of them by doing effectively the same thing (such as updating a drawing), you can cut down on the amount of code you write -- and make it more maintainable -- as follows. In your model object, defining (using setKeys:triggerChangeNotificationsForDependentKey:) a key that is dependent on those that you would observe, and observe just that instead. This also means that you can add and remove dependent keys in one place rather than potentially multiple places throughout your codebase.


Batch modification of collections

Sometimes you need to add or remove a large number of objects to or from a collection in a single logical operation. If you do, you should avoid repeated invocations of methods that add or remove a single element at a time. In particular, you should not yourself repeatedly invoke, for example, add<Key>Object: or remove<Key>Object: since these methods result in a KVO change notification each time you call them. Instead, you should modify the proxy object returned by mutableArrayValueForKey: or mutableSetValueForKey:. The proxy will emit an appropriate, single, KVO chage notification if you use methods such as addObjectsFromArray:, removeObjectsInArray:, or minusSet:.

To ensure that the collection is itself modified as efficiently as possible, you should implement the methods recommended in the NSKeyValueCoding Protocol Reference. If your collection is an array, you can improve efficiency further by also implementing modifier methods that emit their own KVO change notifications. For example, if you want to add a number of new employees to an 'employees' array, you might implement a method like the following.

- (void) addObjectsToEmployeesFromArray: (NSArray *)otherArray
{
   if ([otherArray count] > 0)
   {
       NSRange range = NSMakeRange([self countOfEmployees], [otherArray 
count]);
       NSIndexSet *indexes = [NSIndexSet indexSetWithIndexesInRange:range];
       
       [self willChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes 
forKey:@"employees"];
       [[self employees] addObjectsFromArray:otherArray];
       [self didChange:NSKeyValueChangeInsertion valuesAtIndexes:indexes 
forKey:@"employees"];
   }
}

This means that the proxy array does not repeatedly invoke insertObject:inEmployeesAtIndex:.


Managing a non-array collection, and filtering

As noted above, if you bind an array controller to another object, and want to modify the array programatically, you should use indexed accessors. Indexed accessors also mean, however, that the "array" doesn't have to be an array -- all that matters is that the accessors return a suitable value.

In this example, a combatant has three weapons, each represented by an individual instance variable. They're managed, however, by an array controller which uses indexed accessor methods defined in the Combatant class to retrieve the appropriate weapon. The array count is "fixed" at 3. A further intricacy is that the window displays two table views, one listing all the combatants, the other is a filtered list of combatants -- all the combatants except the one selected in the first table view.
Combatants Window

All this and more is set up in a single window, so without File's Owner the table views' bindings are established programatically. This is a comparatively complex set-up, but the actual code required to implement it (as opposed to just creating default variables, and accessor methods) is quite straightforward, and there's not much of it. IMHO this example is worth looking at...

(Thanks to Kent Signorini for posting the original question for which this was a solution.)


(Missing) KVO notification old and new values

If you observe a property of an object controller (such as an instance of NSArrayController) and request old and new values, you do not get the values in the observation. For example, suppose you observe an array controller like this:

[arrayController addObserver:self
        forKeyPath:@"arrangedObjects"
        options:(NSKeyValueObservingOptionNew |
        NSKeyValueObservingOptionOld)
        context:MyContext];

then implement the observation method:

- (void)observeValueForKeyPath:(NSString *)keyPath
    ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
    /*
    Should be able to use
    id oldValue = [change objectForKey:NSKeyValueChangeOldKey];
    but the dictionary doesn't contain old and new values...
    */

The change dictionary doesn't contain old and new values. This is not your fault, it's due to a bug in the controller. This bug will not be fixed in the forseeable future.


Binding across nibs

You create bindings across nib files just as you would any other binding -- by following key paths.

For example, suppose you have an NSDocument class that contains an array of objects, but the table view, its parent window, and the associatated array controller reside in a nib file managed by an NSWindowController instance. You can set the content of the array controller by following a key path through the window controller (the File's Owner) to its document and so to the array (bind to [File's Owner].documement.array). If you need to make multiple bindings to the document instance, in the window controller's nib file you could instantiate an NSObjectController and set its content to be the document (bind its Content Object to [File's Owner].documement).

You follow the same general pattern for a non-document-based application. You simply need to find a suitable key path from an object in one nib to the object in another nib -- for example, through the applications's delegate.


Undo

Just because you're using bindings doesn't mean that all other Cocoa design patterns are suddenly irrelevant. Target-action still works fine, for example, and is still the right or easiest thing to use in many circumstances.

There are two clear paths to marking a document as dirty:

These paths may be largely orthogonal to bindings. You still have to write some code somewhere, whether you're using bindings or not. You might use the "traditional" technique (as illustrated by, for example, Sketch), and put undo code in your accessor methods:

- (void)setDrawsStroke:(BOOL)flag
{
if (_gFlags.drawsStroke != flag)
{
    [[[self undoManager] prepareWithInvocationTarget:self] 
        setDrawsStroke:_gFlags.drawsStroke];
    _gFlags.drawsStroke = (flag ? YES : NO);
    [self didChange];
    }
}

Although this works, academically it does have the disquieting aspect that it requires a model object to know about its undo manager. KVO allows for a new undo architecture. Using KVO, data objects send notifications when they change. An omniscient observer (your controller object) can watch for changes, and when a change occurs register a suitable undo event. There is an example of an application that uses this architecture in chapter 7 of the second edition of Aaron Hillegass' Cocoa Programming for Mac OS X -- see 07_NSUndoManager.


Bindings in an IB Palette

If you implement a subclass of NSView and create an Interface Builder palette for that subclass, you must in your subclass' bind:toObject:withKeyPath:options: method call super's implementation. If you do not, then when you establish a binding in Interface Builder, the binding values disappear and the binding names become greyed out and uneditable.


Memory Management and Retain Cycles

The following rules apply to bindings and memory management:

Controllers (in the context of bindings) should not retain views. Model objects should not retain controllers. "Pictorially":

Model <--retains-- Controller <--retains-- View

When an object is de-alloced, it should (obviously, following standard memory management rules) release any objects to which it was bound. If you implement your own binding (i.e. bind:toObject: withKeyPath:options: and unbind:), then your unbind: method should release any previously-bound object for the specified binding.

On Mac OS X version 10.3, in some situations (notably using NSWindowControllers) you may find that documents are not properly released when closed, leading to leaks. This is a (well-) known issue, due to retain cycles introduced by the window controller. One workaround is described by Scott Stevenson. This issue is resolved in Mac OS X version 10.4.


"Multiple" binding

The behaviour of "multiple" bindings, such as editable, is a logical 'OR' or a logical 'AND'. Consult the documentation for the specific binding to determine which it is!


NSTextView

The data binding for an NSTextView expects RTFD data archived into an NSData object. You can therefore bind the data binding of an NSTextView to an NSData variable.

You can bind an NSString to an NSTextView's value binding by turning off the NSTextView's Multiple fonts allowed attribute -- the value binding then appears in the Bindings Inspector. Note that if you had previously made a binding to data, it will persist (it shows up as a Parameter binding at the bottom of the Bindings Inspector) -- you must unbind it otherwise you'll get a runtime error.


Uneditable NSTextField becomes editable

[Note to self as much as anything, after a moment of stupidity...] If in Interface Builder you unset the Editable flag for a text field, you may find that after you've established a binding it becomes editable at runtime. Note that the value binding has a Conditionally Sets Editable option -- ensure that this too is unchecked.


NSUserDefaultsController

This example uses NSUserDefaultsController to manage a number of preferences -- a string, a number, a color, and a font. The combination is displayed as a preview. To be stored in user defaults, the color must be archived; a standard value transformer is used to archive and unarchive the color. The font information is stored as a pair of values -- font name and size. The name is the NSFont's fontName, however a custom value transformer is used to retrieve the font's displayName for display. The font details are presented using a text field's displayPatternValue binding.
Controlled Preferences Window

Arrays

On Mac OS X 10.3, like NSUserDefaults, the values returned from NSUserDefaultsController are immutable. In many simple cases this does not matter, since you replace an entire entry (for example, substituting one string with another). In the case of collections (such as arrays), however, you generally want to modify the contents of the collection. To do this you need to transform the immutable values into mutable values. For an illustration, see Uli Zappe's example. On Mac OS X 10.4, the Shared Defaults Controller now provides mutable objects. Uli's sample contains a Tiger-specific example that illustrates the new behaviour.

Registration Domain

NSUserDefaultsController's initial settings are not saved into the registration domain, so you cannot retrieve them from standard user defaults. If you save new defaults, you can retrieve them from standard user defaults.

For example, given:

    [[NSUserDefaultsController sharedUserDefaultsController] setInitialValues:
	[NSDictionary dictionaryWithObject:@"smtp.mac.com" forKey:@"smtpServer"]];

and no other changes to user defaults:

    NSLog(@"NSUserDefaultsController: %@",
        [[[NSUserDefaultsController sharedUserDefaultsController] values]
                valueForKey:@"smtpServer"]);
    NSLog(@"NSUserDefaults: %@",
        [[NSUserDefaults standardUserDefaults] stringForKey:@"smtpServer"]);

returns

NSUserDefaultsController: smtp.mac.com
NSUserDefaults: (null)

If you now make a change (for example, to "smtp.myisp.com") and save, you would get

NSUserDefaultsController: smtp.myisp.com
NSUserDefaults: smtp.myisp.com

If you revert to initial settings, you would get back to

NSUserDefaultsController: smtp.mac.com
NSUserDefaults: (null)

Summary: If you are using the user defaults controller, you should always use [[NSUserDefaultsController sharedUserDefaultsController] values] to access defaults. (Or, of course, register the defaults in standardUserDefaults independently -- you have the dictionary to hand...)


Manual Bindings

You can establish bindings programatically using bind: toObject: withKeyPath: options:. For details of this method, and more on creating bindings see the online documentation.

Most bindings have a variety of options. These can be set programatically using the options dictionary.

Binding Options

Key-value coding has been extended to include a consistent API for attribute value validation, which is also supported by the controller layer. There's an example of manual binding creation including validation here.
Manual Bindings Window

NSUserDefaultsController can be tricky. Recall that collections retrieved from user defaults are immutable, so if you're storing lists in user defaults you may have to create mutable copies before you can modify them. Also, you can't store colours directly in Preferences, so you'll have to use an NSUnarchiveFromData value transformer. You might therefore end up with code that looks something like this:

    NSMutableDictionary *bindingOptions = [NSMutableDictionary dictionary];
    [bindingOptions setObject:NSUnarchiveFromDataTransformerName
            forKey:@"NSValueTransformerName"];
    // other options
    [textField bind: @"backgroundColor"
        toObject: [NSUserDefaultsController sharedUserDefaultsController]
        withKeyPath:@"values.backgroundTextColor"
        options:bindingOptions];

Note also the subtlety in the above example. The backgroundColor variable is not exposed as an available binding in Interface Builder; you can nevertheless establish your own binding programatically. There is no need to subclass NSTextField, override +exposeBinding, and create a palette, simply to make bindings to existing instance variables (unless you really want to do it in IB). That said, in a shipping product you should not rely on this behaviour. Exposed bindings should be thought of in the same way as public API; conversely non-exposed variables are private API. At some point in the future your application might break. You should subclass NSTextField and deal with the binding yourself.


Tableviews

Implementing row deletion by subclassing NSTableView

The following example shows how you can catch Delete key presses to remove rows from a table view. It also illustrates a more general principle of capturing bindings information in a subclass of a bindings-enabled class by overriding the bind:toObject:withKeyPath:options: method. This may be useful if your subclass needs to know what bindings have been established, as there is currently no API to discover this. Typical implementation of the bind:... method is documented here.

1. Subclass NSTableView, with the following:

//  SampleTableView.h
#import <Cocoa/Cocoa.h>

@interface SampleTableView : NSTableView
{
	NSArrayController	*tableContentController;
	NSString * tableContentKey;
} 
@end


- (void)bind:(NSString *)binding toObject:(id)observable
	withKeyPath:(NSString *)keyPath options:(NSDictionary *)options
{	
	if ( [binding isEqualToString:@"content"] )
	{
		tableContentController = observable;
		[tableContentKey release];
		tableContentKey = [keyPath copy];
	}
	[super bind:binding toObject:observable withKeyPath:keyPath options:options];
}


- (void)keyDown:(NSEvent *)event
{
	unichar key = [[event charactersIgnoringModifiers] characterAtIndex:0];
    
	// get flags and strip the lower 16 (device dependant) bits
	unsigned int flags = ( [event modifierFlags] & 0x00FF );
    
	if ((key == NSDeleteCharacter) && (flags == 0))
	{ 
		if ([self selectedRow] == -1)
		{
			NSBeep();
		}
		else
		{
			[tableContentController removeObjectsAtArrangedObjectIndexes:
				[self selectedRowIndexes]];
		}
	}
	else
	{
		[super keyDown:event]; // let somebody else handle the event 
	}
}


- (void)unbind:(NSString *)binding
{
	[super unbind:binding];
	
	if ( [binding isEqualToString:@"content"] )
	{
		tableContentController = nil;
		[tableContentKey release];
		tableContentKey = nil;
	}
}
@end

2. Update your NIB to use the new NSTableView subclass.

3. Bind the NSTableColumns' "value" bindings to an NSArrayController as normal.

4. You must also bind this NSTableView's "content", "selectionIndexes", and "sortDescriptors" to your chosen array controller (using the Controller Keys "arrangedObjects", "selectionIndexes", and "sortDescriptors" respectively). (The auto-binding that NSTableView normally does for you is done via SPI and not caught by our subclassing.)

(Courtesy of Eric Seidel; enhancements by Larry Gerndt and Drew McCormack.)

Disabling sorting in a tableview

(Courtesy of James Dempsey.)

To disable sorting in a tableview you need to do the following:

  1. Select the table view itself and inspect its bindings.
  2. You will see three Table Content bindings for the table view:
  3. Bind the content to:
  4. Bind selectionIndexes to:
  5. Leave sortDescriptors unbound

Typically you only need to bind each table column value binding to the arranged objects of an array controller. When you do this, behind the scenes, all three table view content bindings are automatically 'hooked up' for you. It is only when you need to change these three default content bindings that you need to visit the table view's bindings.

You can also use this mechanism to do other things with a table view's bindings (like pull the selection indexes from one array controller, while displaying the arranged objects of another array controller).

Drag and Drop

Just because you're using bindings, doesn't mean you can't connect a tableview's datasource and delegate outlets, say to support drag and drop. This example shows how to implement the methods in a custom NSArrayController subclass. In addition to drag and drop of rows between windows, it also supports drag and drop of URLs to and from the table view and reordering of the contents.
Bookmarks Window

Adding Columns Dynamically

It is possible to add table columns dynamically to a table view, and to establish bindings on the fly. In many cases, however, if it's possible to do, it is likely to be easier simply to create and bind the full table view in Interface Builder and hide those columns you don't want.


Filtering Array Controller + Object Initialisation

This example displays a table view showing first and last names -- managed by a subclass of NSArrayController -- and an NSSearchField. The array controller uses the text in the search field to filter the objects displayed in the table view. (Thanks to Ron L-S.) It also illustrates object initialisation using NSArrayController's newObject method.


Popups

In popups, the content defines what the collection of objects is; contentValues allows you to define what attribute of the content will be displayed; and the selectedObject obviously reflects the selection -- but importantly to the selected object rather than the displayed value. Typical bindings might be:

This example is a document-based "ToDo" application. ToDo items are displayed in a table view managed by an NSArrayController. The priority of an item is set using a popup menu either in the tableview, or in separate a detail view. The priority determines the color of the item's "title" in the table view, using a value transformer. Each item also has a "due by" date; if the due date is past, the date is highlighted in red, again using a value transformer. The application also illustrates sorting a column in a table view using a custom sort key.
To Dos Window

Mixed static and dynamic content

It is possible to have a popup button mixed with static and dynamic items using bindings. Assuming the content is managed by an NSArrayController, set the array controller's contentArray to be a variable provided by indexed accessor methods. In the indexed accessor methods you can "fake" the return values to return, for example:

-(int)countOfMenuItems
{
    return ([dynamicItems count] + [staticItems count]);
}

- objectInMenuItemsAtIndex:(unsigned int)index
{
    int staticCount = [staticItems count];

    if (index < staticCount)
    {
        return [staticItems objectAtIndex:index];
    }
    return [dynamicItems objectAtIndex:(index - staticCount)];
}

and so on... For a vaguely-related example, see Combatants, which "fakes" an array from three instance variables.


NSImageView

NSImageView's valueURL binding accepts both a URL and an NSString representation of a URL. Placeholders, however, must be NSURLs. You can set the placeholder programmatically, or implement a value transformer to handle nil values.


WebKit

The only "visible" binding for a WebView is hidden ( :-) ). Other bindings are made via other controls.


More Cunning Stuff

(I.e. I didn't have a category for it yet.)

Managing a to-many relationship

Sometimes you need to present the user with a list of objects from which to make a selection. One user interface convention is to provide two table views, one showing the set of objects in the selection, the other showing the remainder of the available objects. There are then "<<" and ">>" buttons to move objects from one table view to the other. (I'm sure there's a technical name for this -- if anyone knows, please tell me!)

This example uses a custom array controller to manage the full set of objects, and filter the display to remove the set in the user's selection shown in a second table view. It also supports addition of object to and removal of objects from the selection.
Groups Window

Bindings with graphics

This example illustrates the use of bindings with views in two contexts:


Graphics Bindings Window