2006
11/28

Looking at Core Data through NSBrowser







6Tringle's featured product: PhotoClay Logo Photo mushing fun with your finger

Buy app through iTunes








We enthusiastically recommend:

Layers of Onions

NSBrowser is an excellent way of interfacing with semi-hierarchically organized data. NSBrowser is part of the NextStep legacy, but achieved real fame through its iPod variant.

One reason its effectiveness is that the user can navigate quickly through large sets of data organized by layered attributes. Even more significant, is that the NSBrowser automatically consolidates the user's focus on the current layer. Not only are we navigating large data quickly, but interaction with the current subgroup of data is enhanced by de-emphasizing the remaining data set. This is similar Spotlight's GPU-enhanced screen dimming effect: reduce the user's depth of focus.

The well-known Core Data is the best Apple method of handling large sets of data and NSBrowser is the best method of interacting with large sets of data. Surely, there must exist some illicit IB magic bindings or a hidden NSMagicClass capable of combining these parts into a coherent whole. One could easily imagine that an elegant and flexible method to intertwine the powerful data API with the stunningly efficient presentation would be an obvious and welcome chunk of Cocoa.

Woefully, the answer is no. NSBrowser's effectiveness is inherently linked to its reliance of hierarchical data. Part of Core Data's strength is that it does not require the model to be hierarchical. In order for Core Data to map to NSBrowser, it is up to the programmer to manually map a model graph to a hierarchal one.

In some cases, this is trivial. Certain modal graphs are already hierarchal. Our first example is a simple location database. Countries have States, States have Cities, etc. This is a somewhat trivial example, but further articles will expand on the topic.

It's Like Pulling Ducks Out of a Hat

On behalf of 6Tringle LLC, I present CoreDataBrowser.dmg! Despite the fact that Steve Jobs would not do this on stage during WWDC, this is a simple exercise. All the work is done in two places: the nib file and CDBrowserDelegate.m.

The secret to getting Core Data in an NSBrowser is in the nib file. There are 9 array controllers. 5 of these are auto-generated by Xcode and IB. The remaining 4 are prefixed with "Browsed" and represent the NSArrayControllers used by the NSBrowser to present the slices of data.

Looking at the nib file, layer slicing is done via bindings. Inspecting the bindings pane of any of the Browsed array controllers will quickly show that the content set is bound to the parent's current selection. For example, if you inspect the BrowsedStateArrayCon, the content set is bound to the CountryArrayController's selection's states. BrowsedCityArrayCon is bound to BrowsedStates's selections' cities and so on. This is chaining of selections to sets is how we can massage core data into smaller collections of arrays suitable for display in an NSBrowser.

Even the most casual observers would notice NSBrowsers similarity to the renowned NSTableView. The programming guide is not exactly a deluge of information, but is more than capable of enlightening those who invest timeffort into it. (Making up words is fun. Squishing two words together? Purecstasy!)

Everyday is a Winding Code

The first thing to notice is that we enumerate our NSBrowser columns in CDBrowserDelegate.h. This is simply good programming practice for static columns. enums are tools for better programmers.

The real work of populating an NSBrowser is done through delegate methods.

  • - (BOOL)browser:(NSBrowser *)sender isColumnValid:(int)column

    The column enumerators allow us to implement this quickly and efficiently. An even better implementation would have an addition CDBrowserInvalidColumn in the enumerator for bounds checking. The invalid column is slightly more resistant to refactoring problems.

  • - (NSString *)browser:(NSBrowser *)sender titleOfColumn:(int)column

    This won't work unless you call [some_browser setTakesTitleFromPreviousColumn:NO]. This is typically done in awakeFromNib.

  • - (int)browser:(NSBrowser *)sender numberOfRowsInColumn:(int)column

    Our array controllers contain our data. We use [some_array_controller arrangedObjects] to get the data.

  • - (void)browser:(NSBrowser *)sender willDisplayCell:(id)cell atRow:(int)row column:(int)column

    Again, the arrangedObjects method is our friend. Combined with objectAtIndex, it becomes trivial to get appropriate Core Data Managed Objects and modify the browser cells.

We make extensive use of switch statements in these delegates. There are more elegant ways, but for a small demo app, it is sufficient.

The remaining code is related to updating the browser and keeping it synced with the array controllers.

There are two cases to account for:

  1. The NSBrowser is clicked and we need to update the array controller's selection.
  2. The underlying Core Data store is modified and we need to update the NSBrowser.

The first case we use - (IBAction)BrowserAction:(id)sender to update the array controllers. The current implementation updates every column. This is potentially a good spot for optimization (but only if Shark says so. POitRoAE.)

The second case is handled through KVO. We set up the delegate object as observers of the array controllers in the awakeFromNib method. Then, in compliance with KVO, we implement the - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context; method. If the array controller adds, deletes or modifies one of its items, we will reload the appropriate column.

The First Hit is Free...

Code mercenaries rejoice, more to follow.

Products of unassailable virtue and rectitude

about products blog contact misc xml