4

In my macOS application, I'm trying to replicate the Photos.app implementation of NSSegmentedControl in NSToolbar to control an NSTabViewController. For reference, here's what that looks like:

Photos.app in macOS Sierra.

So, my approach was as follows:

  1. Hide the default NSTabView header using the Interface Builder
  2. Programmatically add an NSToolbar
  3. Insert NSSegmentedControl as an NSToolbarItem.
  4. Use a #selector to listen for changes to NSSegmentedControl.

Here's the current implementation:

class WindowController: NSWindowController, NSToolbarDelegate {

    // MARK: - Identifiers

    let mainToolbarIdentifier = NSToolbar.Identifier("MAIN_TOOLBAR")
    let segmentedControlIdentifier = NSToolbarItem.Identifier("MAIN_TABBAR")

    // MARK: - Properties

    var tabBar: NSSegmentedControl? = NSSegmentedControl(labels: ["One", "Two"], trackingMode: NSSegmentedControl.SwitchTracking.selectOne, target: self, action: #selector(didSwitchTabs))
    var toolbar: NSToolbar?
    var tabBarController: NSTabViewController?

    // MARK: - Life Cycle

    override func windowDidLoad() {
        super.windowDidLoad()

        self.toolbar = NSToolbar(identifier: mainToolbarIdentifier)
        self.toolbar?.allowsUserCustomization = false
        self.toolbar?.delegate = self

        self.tabBar?.setSelected(true, forSegment: 0)

        self.tabBarController = self.window?.contentViewController as? NSTabViewController
        self.tabBarController?.selectedTabViewItemIndex = 0

        self.window?.toolbar = self.toolbar
    }

    // MARK: - NSToolbarDelegate

    public func toolbar(_ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, willBeInsertedIntoToolbar flag: Bool) -> NSToolbarItem? {

        var toolbarItem: NSToolbarItem

        switch itemIdentifier {
        case segmentedControlIdentifier:
            toolbarItem = NSToolbarItem(itemIdentifier: segmentedControlIdentifier)
            toolbarItem.view = self.tabBar
        case NSToolbarItem.Identifier.flexibleSpace:
            toolbarItem = NSToolbarItem(itemIdentifier: itemIdentifier)
        default:
            fatalError()
        }

        return toolbarItem
    }

    public func toolbarAllowedItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return [segmentedControlIdentifier, NSToolbarItem.Identifier.flexibleSpace]
    }

    public func toolbarDefaultItemIdentifiers(_ toolbar: NSToolbar) -> [NSToolbarItem.Identifier] {
        return [NSToolbarItem.Identifier.flexibleSpace, segmentedControlIdentifier, NSToolbarItem.Identifier.flexibleSpace]
    }

    // MARK: - Selectors

    @objc func didSwitchTabs(sender: Any) {

        let segmentedControl = sender as! NSSegmentedControl

        if (segmentedControl.selectedSegment == 0) {
            self.tabBarController?.selectedTabViewItemIndex = 0
        } else if (segmentedControl.selectedSegment == 1) {
            self.tabBarController?.selectedTabViewItemIndex = 1
        }
    }

}

And, here it is in action:

Implementing NSSegmentedControl in NSToolbar to control NSTabViewController.

Now, I am new to macOS development and this feels like it's a very complicated and convoluted way of solving this problem. Is there an easier way I could achieve the same thing ? Perhaps somehow in Interface Builder ? What could be done to improve here ? What have I done wrong ?

Thanks for your time.

Sam Fischer
  • 1,442
  • 19
  • 35
  • 2
    You can do everything in IB except for the action method `didSwitchTabs` where you can do `self.tabBarController?.selectedTabViewItemIndex = segmentedControl.selectedSegment`. – Willeke Aug 15 '17 at 08:19

1 Answers1

1

enter image description hereFor anybody implementing NSSegmentedControl on the toolbar and it did not trigger IBAction, I got the same problem and pay my half-day to resolve this. The problem is I connect my segmented with the NSWindowController class.

To fix this, create a subclass of NSWindow, set that class to base class of window on your storyboard, then create @IBOutlet @IBAction link to NSWindow. Remember, link it with NSWindowController will not work.

Trung Phan
  • 923
  • 10
  • 18