15

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?

4
  • Just stick it in the app delegate as a property... (joking, mostly.) But what keypaths exist to parent views in OSX storyboards? Commented Nov 5, 2014 at 18:03
  • 1
    NSViewController has a parentViewController property. But if I add an array controller to the split view controller and bind the table view column's Value binding to the table view controller using a model key path of parentViewController.arrayController.arrangedObjects, the app crashes saying the class is not KVC-compliant for the key arrayController. I added an outlet for the array controller to my split view controller subclass and connected the outlet to the array controller I created in IB, and the same crash occurs. Commented Nov 5, 2014 at 19:10
  • I was reading this again... Perhaps override the parentViewController getter to return the specific subclass that has your arrayController property? That's ugly, though. Commented Mar 3, 2015 at 1:43
  • I tried overriding the parentViewController getter, and I ended up with an empty table. Thanks for the suggestion. I've concluded it's currently not possible to share an array controller with Mac storyboards. Maybe Apple will add this capability in OS X 10.11. Commented Mar 3, 2015 at 6:06

1 Answer 1

21

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 NSArrayControllers 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

Sign up to request clarification or add additional context in comments.

8 Comments

Impressive. I downloaded your example, and it works. I wish I would have this answer months ago. I gave up on using bindings for my current project, and I'm not switching back, but I will keep this answer in mind for future projects that use storyboards.
Thanks! I was about to give up on bindings for the project I was starting as well, but I felt like a solution must be possible! Then it occurred to me that it was really just a matter of keeping the selections in sync! :)
Have you got this to work for Core Data? I had a similar idea to what you've done, but while I can get the selection index and prove it's being updated from one scene, I always end up with an empty array controller in the second scene.
Never mind, it does work! The key seemed to be to put a selectedIndexes variable in the Document.swift, which is set as the representedObject of every NSViewController, so you can be sure each gets the same selectedIndexes. You need the Document as the representedObject anyway, so you can set the managedObjectContext of all the NSArrayControllers for Core Data.
Fantastic example, this is helpful even when using storyboards without bindings.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.