I have a Mac document-based Core Data application that uses storyboards. The storyboard has the following layout:
Window Controller
Split View Controller
Table View Controller
Text View Controller
My Core Data model contains a Chapter entity that contains two attributes: title and contents. I want the table view to show each chapter title. The text view shows the contents of the selected chapter.
If I was using a xib file, I would add an array controller to the xib file. I would bind the array controller to File's Owner to access my NSPersistentDocument subclass. I would bind the table view to the array controller's arrangedObjects property and bind the text view to the array controller's selection.
But with storyboards things get more complicated. I can add an array controller to the table view controller, bind the table view to the array controller, and have the chapter titles show up in the table view. But the text view controller can't bind to that array controller because the array controller is in another scene.
How do I add an array controller in Interface Builder so that both the table view controller and text view controller can access it and bind to it?
The key to making this work is to have a NSArrayController
instance in each of your descending NSViewController
subclasses and binding them together through a central data source (most likely your NSDocument subclass). You can then set this data source as your NSViewController
subclasses representedObject
by passing it down through your descending
controllers. Here is an example of a storyboard application with an NSWindowController
which has a content view controller that is a NSSplitViewController
with two child view controllers (A Master / Detail setup):
class Document: NSDocument {
var dataSource: DataSource? = DataSource()
...
}
class DataSource: NSObject, NSCoding {
var items: [Item] = []
var selectionIndexes: NSIndexSet = NSIndexSet()
...
}
class WindowController: NSWindowController {
override var document: AnyObject? {
didSet {
if let document = self.document as? Document {
self.contentViewController?.representedObject = document
}
}
}
}
class SplitViewController: NSSplitViewController {
override var representedObject: AnyObject? {
didSet {
for viewController in self.childViewControllers as! [NSViewController] {
viewController.representedObject = representedObject
}
}
}
}
The trick is to bind the representedObject
to each of your descending view controller's NSArrayController
in the storyboard. You need to bind NOT ONLY the contentArray
BUT ALSO the selectionIndexes
.
The result is that the selectionIndexes
on both descending NSArrayController
s are kept in sync because they are bound through the central data source (DataSource
subclass in above example).
To make this all clearer I have created an example project that demonstrates this here: https://github.com/acwright/StoryboardBindingsExample