swiftcocoabindingkey-value-observingnstabview

Swift/Cocoa: How to bind a value to currently selected tab


Working on a Preference Pane. I've got an NSTabView; tabs are created dynamically from a XIB. They're all very similar, but not identical. So, I need some values to change (and some controls to appear or disappear) according to which tab is currently selected.

Normally one would think it would be possible to bind to NSTabView.selectedTabViewItem; but this does not work. Why? The value is never updated.

Then I got more creative and thought of making a computed property that returns the appropriate value for the binding to observe. Still no dice. The bindings get evaluated when the app is started and never get updated.

Then I decided to go messy and made a delegate to implement the tabView's didSelect method, whereby the currently selected tab is written to a stored property that the binding can observe.

This last approach works, but it feels very clunky and dirty. Does anyone know a better way?

Edit: I'm attempting to implement the NSTabViewController but I just can't get it to work!

If I manually bind the NSTabViewController to the NSTabView object I made in IB, everything appears to be initialized properly, but the tabs never show up. And, if I try to initialize everything in the controller, and then assign the NSTabView via the NSTabViewController.tabView property, I get

A TabView managed by a TabViewController cannot have its delegate modified

Which is odd, because I am trying to do it the way the do cumentation specifies. In fact, even if I try to do TabViewController.tabView = NSTabView() I get the same error. Is this a bug?

Here's the entire relevant portion of my code,

@IBOutlet weak var theTabView: NSTabView?
@IBOutlet weak var tabViewDelegate: NSTabViewController?
    override func assignMainView() {
    ControllersRef.sharedInstance.tabViewController = self.tabViewController
    ControllersRef.sharedInstance.theTabView = self.theTabView
    ControllersRef.sharedInstance.thePrefPane = self
    let tabs = ["Internet", "URL Schemes", "Uniform Type Identifiers", "Applications"]

    if let tabViewDelegate = self.tabViewDelegate {
        for tab in tabs {
            let newTab = NSTabViewItem(viewController: NSViewController.init()) // I tried doing it the other way around also, i.e. via addChildViewController() and tabItem(for:), but the result was the same.

            newTab.label = tab
            let newTabViewItemView = DRYView.init()
            newTabViewItemView.nibName = "SWDAPrefpaneTabTemplate"

            tabViewDelegate.addChildViewController(newTab.viewController!)
            newTab.viewController!.view = newTabViewItemView

        }
        let views = tabViewDelegate.childViewControllers
        tabViewDelegate.tabView = ControllersRef.sharedInstance.theTabView

    }
    super.assignMainView()
}

Solution

  • You should be able to use an NSTabViewController and its selectedTabViewItemIndex property, which is specifically documented to be bindings-compliant.

    If you need to, you can create a computed property that's built on top of selectedTabViewItemIndex to map to an appropriate model object for the selected item. When you do that, be sure to implement the class method keyPathsForValuesAffecting<Key> to return ["selectedTabViewItemIndex"] so that KVO knows to consider your computed property as changed whenever selectedTabViewItemIndex changes.

    (This is probably not sufficient to make the computed property you already tried work, because NSTabView's selectedTabViewItem itself is not documented as being KVO-compliant.)