How to bind a 1:many relation in Interface Builder
I have a simple Core Data app with to model objects: employee and notes. Employees can have many notes (1:many), each note belongs to one employee (1:1). In the employee window I display a list (NSTableView) of employees on file using an NSArrayController. Clicking on an employee displays their details next to the list (name address, gender etc.). Now, I would also like to display a list (also NSTableView) of the notes on file for the selected employee. The list should display 2 attributes of each note on file: its topic and its entry date).
I tried to do the following: (1) bind the date column of the table to the Employee controller, controller key: selection, model key path: notes.date (2) bind the topic column of the Employee controller, controller key: selection, model key path: notes.topic
However this throws an exception (unrecognised selector).
How do I correctly bind the notes attribute of an employee to the NSTableView? Can it be done "directly" using cocoa bindings in IB or is additional code required to access the notes attribute of an employee before I can set up the bindings? I checked the data written by Core Data and this looks all good.
Many thanks for your help...
EDIT: here's the stack trace from the debugger...
2011-09-30 00:46:56.010 EmployeeDemo[3843:707] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[_NSFaultingMutableSet objectAtIndex:]: unrecognized selector sent to instance 0x10260b9c0' *** First throw call stack: ( 0 CoreFoundation 0x00007fff8c654986 __exceptionPreprocess + 198 1 libobjc.A.dylib 0x00007fff8b040d5e objc_exception_throw + 43 2 CoreFoundation 0x00007fff8c6e05ae -[NSObject doesNotRecognizeSelector:] + 190 3 CoreFoundation 0x00007fff8c641803 ___forwarding___ + 371 4 CoreFoundation 0x00007fff8c641618 _CF_forwarding_prep_0 + 232 5 AppKit 0x00007fff87c72648 -[NSTableBinder _visibleRowIndexesForObject:] + 267 6 AppKit 0x00007fff87c72291 -[NSTableBinder observeValueForKeyPath:ofObject:change:context:] + 131 7 Foundation 0x00007fff8dbb818a NSKeyValueNotifyObserver + 387 8 Foundation 0x00007fff8dbb21af -[NSObject(NSKeyValueObservingPrivate) _notifyObserversForKeyPath:change:] + 756 9 AppKit 0x00007fff876f1f0b -[NSController _notifyObserversForKeyPath:change:] + 206 10 AppKit 0x00007fff877fc5fc -[NSController observeValueForKeyPath:ofObject:change:context:] + 847 11 AppKit 0x00007fff879a1cf9 -[NSArrayController observeValueForKeyPath:ofObject:change:context:] + 98 12 Foundation 0x00007fff8dbb818a NSKeyValueNotifyObserver + 387 13 Foundation 0x00007fff8dbd9387 NSKeyValueDidChange + 486 14 Foundation 0x00007fff8dc6834b -[NSObject(NSKeyValueObservingPrivate) _didChangeValuesForKeys:] + 631 15 CoreData 0x00007fff8e827543 _PFFaultHandlerFulfillFault + 3619 16 Core开发者_StackOverflowData 0x00007fff8e825bd8 _PFFaultHandlerLookupRow + 1592 17 CoreData 0x00007fff8e825254 _PF_FulfillDeferredFault + 212 18 CoreData 0x00007fff8e8250d8 _sharedIMPL_pvfk_core + 56 19 CoreData 0x00007fff8e85bd3e -[NSManagedObject valueForKey:] + 222 20 Foundation 0x00007fff8dbb479e -[NSObject(NSKeyValueCoding) valueForKeyPath:] + 348 21 AppKit 0x00007fff8799ded5 -[NSArrayController _multipleValueForKeyPath:atIndex:] + 84 22 AppKit 0x00007fff8799cfc9 -[NSArrayController _singleValueForKeyPath:] + 151 23 AppKit 0x00007fff87704cfe -[_NSControllerObjectProxy valueForKeyPath:] + 77 24 Foundation 0x00007fff8dbb4761 -[NSObject(NSKeyValueCoding) valueForKeyPath:] + 287 25 AppKit 0x00007fff87704baa -[NSBinder _valueForKeyPath:ofObject:mode:raisesForNotApplicableKeys:] + 654 26 AppKit 0x00007fff87704894 -[NSBinder valueForBinding:resolveMarkersToPlaceholders:] + 171 27 AppKit 0x00007fff87907352 -[NSValueBinder _referenceBindingValue] + 31 28 AppKit 0x00007fff87907163 -[NSValueBinder _adjustObject:mode:observedController:observedKeyPath:context:editableState:adjustState:] + 647 29 AppKit 0x00007fff87906e48 -[NSValueBinder _observeValueForKeyPath:ofObject:context:] + 303 30 AppKit 0x00007fff8791ed0b -[NSTextValueBinder _observeValueForKeyPath:ofObject:context:] + 43 31 Foundation 0x00007fff8dbb818a NSKeyValueNotifyObserver + 387 32 Foundation 0x00007fff8dbb21af -[NSObject(NSKeyValueObservingPrivate) _notifyObserversForKeyPath:change:] + 756 33 AppKit 0x00007fff876f1f0b -[NSController _notifyObserversForKeyPath:change:] + 206 34 AppKit 0x00007fff8795e7a5 -[NSArrayController didChangeValuesForArrangedKeys:objectKeys:indexKeys:] + 120 35 AppKit 0x00007fff879601bd -[NSArrayController setContent:] + 870 36 AppKit 0x00007fff879a2664 -[NSArrayController(NSManagedController) _performFetchWithRequest:merge:error:] + 370 37 AppKit 0x00007fff87bb149e -[NSObjectController(NSManagedController) fetchWithRequest:merge:error:] + 177 38 AppKit 0x00007fff87bb1522 -[NSObjectController(NSManagedController) _executeFetch:didCommitSuccessfully:actionSender:] + 92 39 AppKit 0x00007fff87d932e7 _NSSendCommitEditingSelector + 30 40 AppKit 0x00007fff87a50246 -[NSController _controllerEditor:didCommit:contextInfo:] + 188 41 libdispatch.dylib 0x00007fff847df90a _dispatch_call_block_and_release + 18 42 libdispatch.dylib 0x00007fff847e177a _dispatch_main_queue_callback_4CF + 308 43 CoreFoundation 0x00007fff8c5e9c0c __CFRunLoopRun + 1724 44 CoreFoundation 0x00007fff8c5e9216 CFRunLoopRunSpecific + 230 45 HIToolbox 0x00007fff871924ff RunCurrentEventLoopInMode + 277 46 HIToolbox 0x00007fff87199c21 ReceiveNextEventCommon + 355 47 HIToolbox 0x00007fff87199aae BlockUntilNextEventMatchingListInMode + 62 48 AppKit 0x00007fff876de191 _DPSNextEvent + 659 49 AppKit 0x00007fff876dda95 -[NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:] + 135 50 AppKit 0x00007fff876da3d6 -[NSApplication run] + 463 51 AppKit 0x00007fff8795852a NSApplicationMain + 867 52 EmployeeDemo 0x0000000100001a62 main + 34 53 EmployeeDemo 0x0000000100001a34 start + 52 ) terminate called throwing an exception
EDIT 2:
It seems that there's another problem which has to do with the underlying model. The notes for an employee are entered via a separate window with its own controller. It creates the new note and allows the user to set its properties (topic, body text). When the user presses the "Save" button the saveNote method is executed:
- (IBAction)saveNote:(id)sender {
[[self note] setValue: [[self topicField] stringValue] forKey: @"topic"];
[[self note] setValue: [[self textView] string] forKey: @"bodyText"];
[[self note] setValue: [self employee] forKey: @"employee"];
NSError *error = nil;
if (![[self managedObjectContext] save:&error]) {
//todo: add proper error handling
NSLog(@"An error occurred while trying to save the memo");
}
[[self window] close];
}
When I step through this method the exception is actually generated when setting the relation between the note and the employee (3rd line).
What you need is an NSArrayController whose content is bound to the relationship. Then you bind the NSTableView content to the NSArrayController. From there you can do the columns.
UPDATE
This tutorial from Apple might help you out.
It appears that the behaviour described in my question has something to do with having a document based Core Date app or having a "plain" core data app (the check you place when you create a new project in XCode).
I found that when I choose a document based core data app, the above problems do not occur. When I develop the same app as a non-document core data app, binding the notes controller generates the stack trace as shown in my original question and the exception is thrown as soon as I add a note to an existing employee.
So, in all fairness this is just a part of the solution as my experience of Cocoa and XCode is not sufficient to explain why it will not work using the non-document based variant.
精彩评论