bdunagan

Brian Dunagan

April 24 2009
Leveraging setObjectValue: in an NSTableView

Cocoa Bindings enable views and models to effortlessly exchange data. In an NSTableView bound to an NSArrayController, clicking a checkbox immediately updates the corresponding model instance in the bound array. Conversely, updating the model instance's bound property will cause the table to refresh its display of that column. Below is a screenshot from Interface Builder showing this setting and the relevant IB objects.

Using this design pattern translates to much less code than the traditional boiler-plate approach does. But at first glance, we seem to sacrifice control for conciseness; the view-model connection bypasses the controller. Fortunately, NSTableView implements the NSTableDataSource protocol, and it provides a handy hook: setObjectValue:. This selector is fired after every model update, so by implementing it in the controller, we can find out when a model instance changes. While this hook is generally useful, I find it very nice to handle model dependencies.

There are two types of model dependencies: ones between a single instance's properties and ones between multiple instances' properties. I wrote up a small project (Mac OS X 10.5, Xcode 3.1.2), NSTableView_setObjectValue, to illustrate these. To link a single instance's properties in BDObject, I link isChecked with status.

- (void)setIsChecked:(BOOL)newValue {
	isChecked = newValue;

	// Update dependent property.
	[self setStatus:nil];
}

When I click on a checkbox in the running application, the checkmark toggles and the status column instantly reflects that change. Without the setStatus: call, the view wouldn't know the status property had been updated. To link multiple instances' properties, I implement tableView:setObjectValue:forTableColumn:row: in the controller. After a model instance's property is updated, this selector is fired, and I can update the other model instances to reflect this change. (Remember that the table's dataSource property must be connected to the controller object in Interface Builder.)

- (void)tableView:(NSTableView *)aTableView setObjectValue:(id)anObject forTableColumn:(NSTableColumn *)aTableColumn row:(NSInteger)rowIndex {
	if (aTableView == list) {
		// Update dependent instances.
		BOOL newValue = [[[objects arrangedObjects] objectAtIndex:rowIndex] isChecked];
		for (BDObject *object in [objects arrangedObjects]) {
			[object setIsChecked:newValue];
		}
	}
}

Now, when I click a checkbox in the running application, the associated status is updated, along with every other model instance's checkbox and status, linking the instances together. And we didn't need any boiler-plate code to achieve this. NSTableView's setObjectValue: selector elegantly inserts the controller into the view-model bindings path.

Tips for Xcode and Interface Builder Core Animation on the Mac
LinkedIn GitHub Email