Connecting drill-down NSSplitView outlets using Storyboards

| | August 18, 2015

I have an NSSplitView that manages a drill-down hierarchy. The parent/left side displays groups, while the child/right side receives notification that the group selection has changed, and updates to display the child items.

However: When creating an NSSplitView using storyboards, there are 3 scenes created: One for the split view itself, and one for each of the right/left NSViewController instances.

The problem here is that I have two controllers that are also acting as NSTableViewDataSource items, and the parent controller should have an IBOutlet to the child controller so that it can provide direct notification that the selection has changed.

But! Because these controllers are in different scenes, I cannot connect them. Nor can I move them both up to the parent scene for the split view, because they would then not have access to the NSTableView outlets. (Nor would the tables reference the controllers as delegates/datasources.)

Do I need to use NSNotification here? It seems so indirect and wishy-washy, and I haven’t found a segue-based approach on the Mac.

enter image description here

4 Responses to “Connecting drill-down NSSplitView outlets using Storyboards”

  1. Andriy Gordiychuk on November 30, -0001 @ 12:00 AM

    I was having the same problem and have found a workaround. It is not extremely elegant and it does not allow you to connect outlets with storyboard, but it does allow you to create relationships identical to those with IBOutlets.

    The key is to implement a singleton class:

    import Foundation
    import Cocoa
    
    class SplitViewConnector {
        static let sharedInstance = SplitViewConnector()
    
        var masterController:NSViewController?
        var detailController:NSViewController?
    }
    

    Now in awakeFromNib method of your master controller you can set a master relationship and, similarly, you can set a relationship for your detail controller.

    If, for example, your master controller needs to have a direct relationship to your detail controller you can also implement it. Assuming that the class of your master controller is YourViewController and the property for accessing your details controller is called myDetail it would look like:

    var masterController:YourViewController? {
        didSet {
            self.assignDirectConnections()
        }
    }
    
    var detailController:NSViewController? {
        didSet
            self.assignDirectConnections()
        }
    }
    
    func assignDirectConnections() {
        if let master = self.masterController {
             if let detail = self.detailController {
                master.myDetail = detail
             }
        }
    }
    
  2. You don’t necessarily want to use an NSSplitViewController. Instead, just drag an NSSplitView into the main view of your Storyboard.

    As Cory explained in his answer, the new NSSplitViewController and NSTabViewController have methods for accessing parent and child controllers. This is useful if you really do want to divide your interface into different Storyboard scenes.

    In your example though, you want all of the views to be in the same scene so that you can connect up all their IBOutlets to the same view controller. So, just use an NSSplitView and don’t bother with an NSSplitViewController at all.

    enter image description here

  3. As you say, Storyboards on OS X (and iOS before that), won’t let you connect IBOutlets between scenes so you need to convey the actions in some other way.

    I would configure the actions/delegates properties on the awakeFromNib method of any of the three controllers. At this moment the NSSplitView and its child controllers will all be created and connected.

    I wouldn’t use notifications when there are no multiple target objects, or the target objects are known and fixed, as in your case.

  4. The new NSSplitViewController and NSTabViewController class are what Apple is calling container controllers. you can get references to other controllers within the container using the new property’s in NSViewController

    @property (readonly) NSViewController *parentViewController
    @property (copy) NSArray *childViewControllers
    

    so for example if you would like to get a reference to your child Table controller from your parent table controller. In your parent table controller viewDidLoad you can do the following

    myChildTableController = [self.parentViewController childViewControllers][1];
    

    or vice versa from your child table controller

    myParentTableController = [self.parentViewController childViewControllers][0];
    

Leave a Reply