The Cocoa Controller Layer
Pages: 1, 2, 3
Table Column Bindings
Bindings between the controller and interface are established individually for each interface element. For our application, each table column is considered an element of the interface, so we must establish a binding between the Title table column and the title property of the model by way of the controller we created. We must also create a binding between the Author table column and the author property of Book.
For now, select the Title NSTableColumn (by whatever combination of clicking and double-clicking that is required to select such a deeply nested control), and open the Bindings inspector for the NSTableColumn. The Bindings inspector lists all of the attributes of the selected interface element that are available to be bound to an controller. The available bindings include properties such as font attributes and text color, as well as availability states such as whether or not the control is editable, enabled, or hidden. Most importantly for our purposes today, we have a binding for the value of the table column, which is what will be displayed in each row of the table column.
To reveal the options for the value binding, click on the disclosure triangle to the left. This exposes a plethora of options; focus only on the first three options. Here we have three parameters that are used to tell the NSTableColumn where to get its data from. Bind to is where we specify what controller we will use to obtain the value from the data model. In this popup you should see Book Controller, which is the controller we want to select.
Next we have the parameter Controller Key. Just like any object, instances of NSArrayController have properties that are accessible via key-value coding. You'll notice that each of the keys in the Controller Key list correspond to methods of NSArrayController. Here we want to choose arrangedObjects, which corresponds to the NSArrayController method that returns the NSArray of data model objects that is managed by the controller; in our case this will be an array of Book objects.
Finally, we want to specify the Model Key Path, which is simply the key used to access the property of the data model whose value we're interested in displaying. Opening this combo box will reveal all of the keys we specified when we configured Book Controller. For the Title column values, we naturally would like to show the title of the book, so we select the title key for this parameter. With that, we have set up a complete binding between the data model, controller, and a user interface component. Creating the binding for the Author column is exactly the same, except we choose author for the Model Key Path rather than title. When you've configured the binding properly, the inspector should look like the following:
Let's move our attention to the three buttons at the bottom of the main window. Again, the Add button is used to add a new record to the table view, the Remove button will delete the selected record in the table, and the Get Info… button will show a detailed view of the selected record. NSArrayController defines a number of methods that can be the action of a control. Two of these actions are add: and remove:. The add: action will tell the array controller to create a new instance of its data model class, and append it to the array of managed objects. The remove: action will remove the selected object from the array controller's content array. What we want to do is drag a connection from the Add and Remove buttons to Book Controller and make their actions add: and remove:, respectively:

Notice that NSArrayController also has an action for inserting new items, as well as for changing the current selection to the next or previous object in the array. You could optionally create controls for these actions.
We are ready to test version 1 of our interface. Do this now in Interface Builder by selecting Test Interface from the File menu. The main window will appear, and you should be able to click the Add button to create a new row in the table, and edit the fields of that row. That, dear readers, is how the controller layer works in Cocoa. Pretty slick stuff if you ask me! If you'd like to see some more slick stuff, try entering in several rows of information, and then click on the table column headers. Sorting! Right out of the box without any work on your part!
Let's get back into Interface Builder and set up the bindings for the Inspector, and add a bit of refinement to the way things work.
Setting up the Inspector Bindings
The Get Info… button needs to be able to open up the inspector window. To do this we simply make the action of this button the makeKeyAndOrderFront: method of the inspector window.
Setting up bindings for the inspector text fields is very similar to what we did for the table columns. Select the title NSTextField, and open its Bindings inspector. Select the value section to disclose the value bindings options. Just like with the Title table column we select to bind the text field to Book Controller, and the Model Key Path is the title key for the Book class. The thing that differs is the Controller Key, which we set to selection. This controller key corresponds to the selection method of NSObjectController, which for NSArrayController returns the currently selected object in the content array. By setting the controller key to selection, the text field will always display the title property of the selected table row. Do the same for the Author text field, binding its value to the author property of the model class, and setting the controller key to selection.
When you test run the interface, you will see how the text fields of the inspector track the current selection. Note how the text fields display "No Selection" when there is no selection in the table view. This string is known as a placeholder, and the string that is displayed can be changed in the value binding inspector for the text field. There are four types of placeholders that you can customize: no selection, multiple values, not applicable, and null.
If you've setup the NSTableView to allow multiple selections, you'll notice that the text fields show the string "Multiple Values" when you have more than one row selected (this is the default multiple values placehold). This is handled intelligently, however. Consider the scenario where we have selected two books in the table: The Odyssey and The Illiad, both by Homer. In the inspector the title field will show "Multiple Values", however, the author field will show "Homer" since all of the selected rows have "Homer" as the author. The emphasis here really is to show when there are multiple values for a single property rather than when there are multiple records selected.
Miscellany
You've probably noticed by now that there are lots of control attributes that we can bind to a controller. One example is in NSWindow. If you open the Bindings inspector for the inspector window itself, one parameter we can bind to is the window's title. Select title from the list of parameters, and let's bind it to the selection key of Book Controller, and the title model key path. This is exactly what we did to set up the value binding of the title text field. Now the title of the inspector window will match the title of the book that is selected in the main table view. Once again, slick stuff!
Another point of refinement we might like to make is to have the Remove button enabled only when there is a selection made. This is easy to do using the controller bindings technology. In the Bindings inspector for the Remove button open up the parameters for the enabled binding under Availability. Choose to bind to the Book Controller, and under Controller Key select canRemove. This is one of the many keys that correspond to methods in NSArrayController that return Boolean values, suitable for use as keys to bind to Boolean properties of user-interface controls, such as whether the control is enabled. You could also set up the Add button enabled availability to be bound to the canAdd key of Book Controller.
With the addition of the controller layer, Cocoa goes even further in achieving the core goal of any application framework: eliminate as much repetitive coding as possible so that the developer can focus his energies on developing innovative and polished features that potentially sets his application apart from the rest of the pack. I hope this article has given you a glimpse of what can be done using the controller layer, as well as an idea of how things work in the background so you can explore other bindings options.
You can download the XCode Project for this article here.
Michael Beam is a software engineer in the energy industry specializing in seismic application development on Linux with C++ and Qt. He lives in Houston, Texas with his wife and son.
Return to MacDevCenter.com.
You must be logged in to the O'Reilly Network to post a talkback.
Showing messages 1 through 35 of 35.
-
I think that this programm is not perfect
2008-06-14 02:27:45 vavdiyaharesh [Reply | View]
Because when we click on Add button it should open new like in ur program window open on Get Info. then you should provide one button in another window to add information
-
adding undo/redo support to controller
2005-01-22 11:06:59 stote [Reply | View]
first I'm brand new at gui & oop - old fortran and more recently matlab dinosaur. In going thru "Vermont Recipes" I'm struck by the amount of effort needed to include undo/redo and to do appropriate notifications to keep model & view in synch.
This controller class is great - but how do I add undo/redo capability to it? Also it seems like there should be an easy way to add save and load capability, since I'm using kvc?
mike
-
What about "separate" data
2004-07-17 08:28:32 swoodnj [Reply | View]
Forgive me if I'm missing something...I got excited when learning about the bindings, I've been working on an extensive database app and have been writing lots of glue code. I understand the concepts and have a demo or two working, but I don't understand how I can use this with external data, in my case a postgresql database. Let's say I bind an Add button to add:, how can I grab the data for my own use? Am I missing something obvious here? I can see where it might be useful with a document based app and NSCoder, but not for my purposes... -
What about "separate" data
2004-10-08 13:35:11 scottellsworth [Reply | View]
There are a couple of ways to handle this.
Likely the easiest is to create an NSArray subclass that has hooks on the accessors. When a new row is added in the interface, eventually, a new item will be added to your NSArray subclass. You can then "do what is appropriate" to get the user entered data, enter it in a database, etc. You can get as fancy as need be - watch for selection changes before commit, update the row with generated keys, etc.
Just make sure that if you programatically change any value in your NSArray behind the back of the controller, that you do one of the things that tells KVO that you made a change.
Scott
-
NSMenuItem and Bindings
2004-05-12 08:28:57 schromosan [Reply | View]
I want to have a menu item that changes from Show to Hide according to the state of a drawer that it will open. I've been struggling with this with no success. How do you bind the menu item's title to a controller to do this?
-
NSData
2004-05-11 14:56:29 psheldon [Reply | View]
I've done some nebulous preverbalized stuff with NSData and similar classes such as alluded to in TextArchitecture.pdf from Apple.
I believe TextIO.pdf shows how to make a non document based application to deal with raw text rather than force use of NSAttributedString or rtfd sort of textstorage method return for NSTextview.
I had seen, in modula, a way to what is called "flatten" objects and save them. I don't know if there is a class or polymorphic method inherited from the big daddy, NSObject, that does this.
I think it would be nice if there were to start to clean up the model making part of MVC.
-
subclass choose custom class added outlet instanteated created code and opened
2004-05-06 08:44:55 psheldon [Reply | View]
Did some toodling on my own.
You can pull the NSArrayController from the palette and then switch to instance pane, subclass it and then have inspector identify it as the custom class for the IB instantiated object set and then create files.
So, I got some connections from a broader map of experience of Mike Beam articles than merely following Mike Beam's current article (I was brave and tapped into my own mind).
It was very fast on a G5 with dual processors to fiddle like this.
I suppose overrides and actions and outlets all require subclasses so you can create code. This article exhibited closure and demonstrated that you didn't have to create any code, but by toodling, I found I could and hadn't lost "that world", but remained open to it.
That's it.
-
sought company courage at library and got my hand in Mike Beam cocoa again
2004-05-04 21:21:04 psheldon [Reply | View]
Hurrah.
It was a fast read, but, I worry, easy in and easy out of my head. But, I council myself, I wasn't ready to really read the system level instructions or discussions on this new technology. I needed my hand held on this one and easy in easy out at first, to get a taste of things to come.
Need more on this and Apple will be supplying, especially if, as Dave (llnldave, in Key paths) wrote, Dave found a bug.
Should I dose myself with dark and pain on "the bottom" now or continue reading top down approaches like this article and wait for pain? I'll wait. But, I was glad to reboot in panther to check this out. I'm not as scared of xcode; I've stretched a bit out of my comfort zone a bit. I don't have to push it until it is painful.
I got controllers part of IB not code. Views were already part of IB. What next, models (all of MVC)? I'll be painting applications from palettes?
Fantastic, just as it should be.
-
sought company courage at library and got my hand in Mike Beam cocoa again
2004-05-04 21:17:07 psheldon [Reply | View]
Hurrah.
It was a fast read, but, I worry, easy in and easy out of my head. But, I council myself, I wasn't ready to really read the system level instructions or discussions on this new technology. I needed my hand held on this one and easy in easy out at first, to get a taste of things to come.
Need more on this and Apple will be supplying, especially if, as Dave (llnldave, in Key paths) wrote, Dave found a bug.
Should I dose myself with dark and pain on "the bottom" now or continue reading top down approaches like this article and wait for pain? I'll wait. But, I was glad to reboot in panther to check this out. I'm not as scared of xcode; I've stretched a bit out of my comfort zone a bit. I don't have to push it until it is painful.
I got controllers part of IB not code. Views were already part of IB. What next, models (all of MVC)? I'll be painting applications from palettes?
Fantastic, just as it should be.
-
Override add?
2004-04-24 09:47:10 kristianmonsen [Reply | View]
Great tutorial on the cocoa bindings. Is there any easy way of overriding the add method? I would like new books to have a default title and author when added to the table so it's easier to see that there is an item there. -
Override add?
2004-07-05 14:19:11 markstone1 [Reply | View]
I just worked out a pretty easy way to set the title/author of newly created books. Hope it helps:
First, subclass NSArrayController in Interface Builder. Nmae it something like MyController. Also, create the files for the new class in your project.
Next, select the BookController instance that was created before and change its 'custom class' to MyController.
Go back to Xcode. Add the following line to MyController.h (it doesn't really matter, but I got a compiler warning otherwise):
#include "Book.h"
Now, override the -(id)newObject: method in MyController.m as follows:
- (id)newObject
{
id newBook = [super newObject];
[newBook setTitle: @"Unknown Title"];
[newBook setAuthor: @"Unknown Author"];
return newBook;
}
Just drag MyController.h into IB, save everything and compile! -
Override add?
2004-08-01 00:32:42 donarb [Reply | View]
Creating a special subclass just to set an object's attributes is way too much work. You'd be better off just putting the code that assigns the default names into the Book class itself, using the init() method. Somewhere down the road you'll be banging your head against the wall trying to figure out where the default name is being set. Put this into the Book object instead:
- (id)init
{
self = [super init];
if (self != nil) {
[self setTitle:@"Unknown Title"];
[self setAuthor:@"Unknown Author"];
}
return self;
}
-
Override add?
2004-07-05 14:18:56 markstone1 [Reply | View]
I just worked out a pretty easy way to set the title/author of newly created books. Hope it helps:
First, subclass NSArrayController in Interface Builder. Nmae it something like MyController. Also, create the files for the new class in your project.
Next, select the BookController instance that was created before and change its 'custom class' to MyController.
Go back to Xcode. Add the following line to MyController.h (it doesn't really matter, but I got a compiler warning otherwise):
#include "Book.h"
Now, override the -(id)newObject: method in MyController.m as follows:
- (id)newObject
{
NSLog (@"newObject:");
id newBook = [super newObject];
[newBook setTitle: @"Unknown Title"];
[newBook setAuthor: @"Unknown Author"];
return newBook;
}
Just drag MyController.h into IB, save everything and compile!
-
Save and Loading the Array-Data
2004-04-12 05:55:02 AlexKid [Reply | View]
Thats a nice article of a great technologie. But could somebody show me the way to save the data colectet in the text view to a file and to load it again. I have some idees but I don t know realy how to do this.
I hope somebody can show me a tuturial or an example program. Or tell me what i have to do.
Thanx AlexKid -
Save and Loading the Array-Data
2004-04-15 05:51:49 kool [Reply | View]
Scott Anguish just showed me how to do this:
There are two ways to connect data to an NSArrayController.. by setting the content, or by binding the contentArray item to the object..
I recommend keeping the array value (in your case, the contents of "MyContent" as a variable of the Document, and then bind the array controller to the File's Owner MyContent key.
Anyways, that's partly moot in this case. loadDataRepresentation:ofType: is called before the nib is loaded. So the nib doesn't have an NSArrayController yet. You need to wait until the windowControllerDidLoadNib: is done.
So, my fix would be
add a "NSMutableArray *myContent" to your document class, and accessors for it.
remove the connection to 'content' in IB
bind the array controller's contentArray to that variable on File's Owner in the nib
- (NSData *)dataRepresentationOfType:(NSString *)aType
{
NSKeyedArchiver *archiver;
NSMutableData *data = [NSMutableData data];
archiver = [[NSKeyedArchiver alloc] initForWritingWithMutableData:data];
[archiver encodeObject:[self myContent] forKey:@"MyContent"];
[archiver finishEncoding];
return data;
[archiver release];
}
- (BOOL)loadDataRepresentation:(NSData *)data ofType:(NSString *)aType
{
NSMutableArray *mutArray;
NSKeyedUnarchiver *unarchiver;
unarchiver = [[NSKeyedUnarchiver alloc] initForReadingWithData:data];
mutArray = [unarchiver decodeObjectForKey:@"MyContent"];
[self setMyContent:mutArray];
[unarchiver finishDecoding];
[unarchiver release];
return YES;
}
-
Slightly different views on the same data?
2004-04-11 11:38:21 look_to_windward [Reply | View]
Hi,
I've switched over to using NSArrayController for an application I'm developing, and it's a very neat fit for a class I already had in there. Gets rid of lots of the glue code, as you mention. However, I've run into a problem which isn't easy to solve given all the simpler examples that apple and yourself have provided.
I have some controllers which are in charge of an array that I want to present to the user in several ways, but customised each time. For example, one of the controllers has an array which is presented in a table, but also used to populate a popup menu with the array plus a few other items.
The problem comes when I want to customise the views of the data - for example in the popup I'd like to say
none
---
array item1
array item...
Before, I had the controller watching for changes in its array, and then manually rebuilding the popup and the menubar menu (again slightly different) when things changed (and of course updating the table as it was the data source).
Ideally I'd like to be able to bind the various objects to different accessors in the controller that returned a slightly modified array depending on where it was being presented. I did try to experiment with manually setting up bindings (since you can't set them up in IB, which is a shame), and I can't seem to get that to work.
The alternative would be to set up a controller for each 'view' on the array, but that doesn't seem right to me (it's tying the controllers very tightly to the different views) - I don't see why you should have multiple controller objects for one array of objects - I'd like to keep all the logic for manipulating that array in one place.
Any help on this would be greatly appreciated. Perhaps I just haven't understood how bindings should work. Closest I've found to a solution is this
filtering example - however that's using a controller for each 'view'. A possible subject for a future article anyway : )
-
Slightly different views on the same data?
2004-05-28 03:42:00 jkp [Reply | View]
Hi...
I'm a new cocoa programmer / learner, but this article made perfect sense knowing something about MVC, and also having used a data object in applescript studio.
I have to say though, I think someone needs to make an attempt to answer the query posted above. This seems to me to be an important limitation if you cannot acheive this affect. Any fruther thoughts from you gurus out there?
-
Jaguar and Cocoa Bindings
2004-04-10 18:57:32 tkokesh [Reply | View]
Just to check... Cocoa Bindings are not available to applications that are going to run in Mac OS X 10.2 Jaguar, correct? -
Jaguar and Cocoa Bindings
2004-04-11 11:42:18 look_to_windward [Reply | View]
yep, 10.3 and later
"Cocoa Bindings is available to Cocoa applications running Mac OS X version 10.3 and later."
apple docs
-
Bindings also work for Java objects...
2004-04-08 20:19:39 schuldar [Reply | View]
Several months ago I did some experimenting and was quite pleasantly surprised to discover that the Cocoa bindings work equally well for Java classes. So go ahead and jar up your properly architected Java application (you did separate business logic and presentation logic right?) and put a nice fancy native UI on your Java application!!
-
...And now?
2004-04-08 01:25:00 isil [Reply | View]
As already stated, that's a great article on a great technology.
But it lacks a crucial (IMHO) point: how the collected dictionary of book objects can be accessed from the myDocument class? this closes the MVC loop, and allows us poor mortals to serialize the library down to a file... -
...And now?
2004-04-15 05:54:05 ANegmAwad [Reply | View]
As the pre-poster said, You can add a key to the binding of the controller, i.e.
Stock
NSString* Name;
NSMutableArray* stuff;
You add an ArrayController and bind it to Stock with the key stuff.
There is no need for an IBOutlet.
-
...And now?
2004-04-09 22:08:56 sanguish [Reply | View]
You'd create an outlet for the Book array in the document class.
(There isn't really any reason to make a custom class Book in this case, as I didn't see anything other than accessors for it.. you could easily use NSMutableDictionary instead
you'd establish a binding from the array controller to the array key (on the File's Owner) rather than connecting it to the content outlet.
Hmmr.. looking at the article, and the source, I don't see where he's connecting the array and the array controller. -
...And now?
2004-04-08 07:22:27 eblubeta [Reply | View]
generally you put an Outlet in YourDocumentType to the controller.
the generic controller keeps an instance var named "contentObject", which you can access by name:
[myControllerobj contentObject]; (big part of KVC btw)
what should follow in your brain is all of the cards falling into place for how to connect to any other data In any other objects in the controller layer.
simply look it up in the new online Apple dev ref docs, and find the name of the offending data, then make "yourclass" aware of the controller in IB, and you can ask for any of its data.
-
Key paths
2004-04-07 15:43:45 llnldave [Reply | View]
Mike,
A minor error in your article. The Model Key Path is not just simply "the key used to access the property of the data model whose value we're interested in displaying". From the ADC web site, "A key path is a string of dot separated keys that is used to specify a sequence of object properties to traverse.". In other words, foo.bar.baz means get the baz property of the bar object in the foo object.
Right now this is broken in Panther. I have a bug report in on this...when it's fixed, KVC and KVO will be even more fun with which to play!
Dave
-
great article
2004-04-07 10:35:15 tim1724 [Reply | View]
This is the best "how-to" I've yet seen for Cocoa Bindings. Great job.
-
Welcome back Mike
2004-04-07 07:16:01 Daniel H. Steinberg |
[Reply | View]
Nice article. Sigh. I guess this means I have to get busy writing the Java column again too.
-
Great article
2004-04-07 01:11:27 sporkstorms [Reply | View]
I just wanted to chime in with another post of thanks. :)
Welcome back. I've sorely missed your Cocoa articles, and hope to read many more in the near future.
-
Thanks and Welcome Back!
2004-04-06 23:25:17 johnts [Reply | View]
Just want to add my thanks and welcome back. I followed all of the other articles and was hoping there'd be more.
This one is great. I've read some about the new Cocoa Bindings, but this article so far explained it the best - nice simple example, with good explainations.
Hope you're back - looking forward to some more!
-
Not "Controller Layer", "Cocoa Bindings"
2004-04-06 21:58:39 sanguish [Reply | View]
When this technology was first released we referred to it as the Controller Layer. But that has since changed, and we now refer to it as "Cocoa Bindings".
It's a fantastic technology.
-
Wonderful
2004-04-06 20:16:49 davtri [Reply | View]
Welcome back Mike..... Wonderful article. It's nice to see that Apple made a worthwhile enhancement to Cocoa with the Controller Layer (now if only Sun would follow along) and your article made it easy to start out with this methodology.
Many thanks and I hope to see more articles by you in the future.
-- Dave Giffin
-- http://www.davtri.com
-
Error in code
2004-04-06 17:38:27 flashbios [Reply | View]
There is an error in the code if you wish to compile the program. In Book.h add the line:
#import <Cocoa/Cocoa.h>
under
#import <Foundation/Foundation.h>
and everything will compile fine. -
Error in code
2004-04-09 17:18:20 jwnestor [Reply | View]
I cant get the project to compile, with or without the above recommendation. I get two errors as follows
PBXCp build/Bibliotecha.app/Contents/Info.plist build/Bibliotecha.build/Bibliotecha.build/Info.plist
cd /Users/jwnestor/Desktop/Bibliotecha
/Developer/Tools/pbxcp -exclude .DS_Store -exclude CVS -resolve-src-symlinks /Users/jwnestor/Desktop/Bibliotecha/build/Bibliotecha.build/Bibliotecha.build/Info.plist /Users/jwnestor/Desktop/Bibliotecha/build/Bibliotecha.app/Contents
/Users/jwnestor/Desktop/Bibliotecha/build/Bibliotecha.build/Bibliotecha.build: No such file or directory
PBXCp build/Bibliotecha.app/Contents/PkgInfo build/Bibliotecha.build/Bibliotecha.build/PkgInfo
cd /Users/jwnestor/Desktop/Bibliotecha
/Developer/Tools/pbxcp -exclude .DS_Store -exclude CVS -resolve-src-symlinks /Users/jwnestor/Desktop/Bibliotecha/build/Bibliotecha.build/Bibliotecha.build/PkgInfo /Users/jwnestor/Desktop/Bibliotecha/build/Bibliotecha.app/Contents
/Users/jwnestor/Desktop/Bibliotecha/build/Bibliotecha.build/Bibliotecha.build: No such file or directory
Running 10.3.3 on a G4. Other projects compile OK. Any thoughts? Thanks





