Apple has a nice little tutorial for making a simple master-detail interface. Interface Builder will even automatically generate one for you from a Core Data entity. However, I'm trying to do something more complicated than the simple example and I've been struggling for a while to get it to work.
I have a Core Data document-based application. The model includes an abstract entity Page, and several concrete subentities of Page. All Pages have some attributes in common (like "name") and those are defined in Page. Obviously, the subentities have the attributes that are unique to them.
I want the interface to allow the user to see all types of Pages in the master list (an NSTableView). When they select a Page, the detail fields that are displayed would depend on what kind of Page it is.
Here's what I have right now:
I have a main nib file, where the master list is displayed, plus all fields common to a Page. There's a nib for each page type with its specific fields. There's the main NSArrayController in the main nib file, which is populating the NSTableView. There's an NSArrayController in each of the page-specific nibs as well so that I can bind the detail fields to attributes of the current selection. All of my NSArrayControllers are configured identically, and I have them all bound to the same managedObjectContext and the same selectionIndexes.
I am using Aaron Hillegass's method for view swapping that he describes in his Cocoa book. So I registered for NSTableViewSelectionDidChangeNotifications, and when I receive one, it calls a method switchView:
switchView looks at the currently selected object, checks which type of Page it is, and swaps in the appropriate nib file according to Hillegass's method.
Everything works fine if I only add pages of one type, but as soon as I add a page of a second type, I get this error:
Error setting value for key path selectionIndexes of object (from bound object entity: Page, number of selected objects: 1): [ valueForUndefinedKey:]: the entity NoColPage is not key value coding-compliant for the key side.
The last part of the error makes sense: it's stuck trying to display the wrong nib, so it's trying to bind to fields that don't exist for this object.
I added a selectionIndexes field to MyDocument so that all of my NSArrayControllers could bind to the same place. I've agonized over this for days and I can't figure it out. Any ideas?
If it helps, here is a sample project you can download. I extracted only the things relevant to this problem from my project into a new dummy application, which I've been using to test and play around.
PS: Interface Builder's tool for generating a master-detail interface from a Core Data entity doesn't work like I want it to for abstract entities. It only creates fields for the attributes in the superentitity.
Edit: I think Joshua is on to something, but unfortunately, it doesn't work--I keep running into the same problem. At first I was having a hard time because I didn't understand that -unbind: expects a string constant, not a key path.
I've tried several variations: where I keep track of the currently displayed nib's array controller; where I keep track of the currently displayed page type and only unbind/rebind when I'm trying to display a different page type...
Here's the relevant section of code.
-(void) displayViewController: (ManagingVC *) vc withClass:(NSString*) className {
//Try to end editing
NSWindow *w = [box window];
BOOL ended = [w makeFirstResponder:w];
if (!ended) {
NSBeep();
return;
}
//The Managing View Controller's NSArrayController
NSArrayController* vcAc = [vc arrCont];
//if the page we're trying to switch to is NOT the same type as the current page we're displaying
//if it is, do nothing.
if (![currPageDisplayed isEqual:className]) {
//unbind the old view controller
ManagingVC *oldvc = [viewControllers objectForKey:className];
NSArrayController* oldsac = [oldvc arrCont];
[oldsac unbind:@"selectionIndexes"];
//bind the new view controller
[vcAc bind:@"selectionIndexes" toObject:self withKeyPath:@"selectionIndexes" options:nil];
currPageDisplayed = className;
NSView *v = [vc view];
//display the new view in an NSBox in the main nib
[box setContentView:v];
}
}