Background

Yeah, quite a mouthful. Anyway, for my first macOS project, I wanted to use an NSSegmentedControl to control the main NSTabViewController of my application. I liked the way the macOS Photos app did it, and I wanted to implement something similar in my project. Here’s what the Photos app implementation looks like:

Photos.app

I think that looks way cleaner than the default NSTabViewController look:

Default NSTabViewController UI

Anyway, I wanted to make better use of space and implement the control in the NSToolbar of my NSWindow.

So, there’s two basic ways of achieving this: through the Interface Builder or programatically.

I’ll show you how to do both.

Using the Interface Builder:

  1. Create a New File > Cocoa Class > Subclass of NSWindowController and name it WindowController.Creating a new NSWindowController class.

  2. Make your main window use this newly created class. In your Storyboard, click on your main window and in the Identity Inspector, set the Class field to use your WindowController class. Assigning WindowController class to NSWindowController object present in the Storyboard.

  3. Drag a Toolbar from the Object Library onto your main window. Adding NSToolbar to NSWindow in Storyboard.

  4. Delete all the items in the Toolbar except the Flexible Space item. You’ll need this to center your NSSegmentedControl.Deleting unneeded items in NSToolbar.

  5. Drag a Segmented Control from the Object Library onto your Toolbar, and add it between two Flexible Space items in the Default Toolbar Items section. Adding NSSegmentedControl to NSToolbar.

  6. Open the Assistant Editor so that both your Storyboard and the WindowController class are showing. With the Segmented Control selected, go to the Connections Inspector and drag the circle next to the action field under the Sent Actions section and create a new IBAction and name it segmentedControlSwitched. Adding connection

  7. Now, in your WindowController class, add the following code:

import Cocoa

class WindowController: NSWindowController {

    var tabViewController: NSTabViewController?
    
    override func windowDidLoad() {
        super.windowDidLoad()
    
        self.tabViewController = self.window?.contentViewController as? NSTabViewController
    }

    @IBAction func segmentedControlSwitched(_ sender: Any) {
        let segmentedControl = sender as! NSSegmentedControl
        self.tabViewController?.selectedTabViewItemIndex = segmentedControl.selectedSegment
    }
}

Make sure your window is hooked up to an NSTabViewController and that your NSSegmentControl has the same number of NSSegmentCells as the NSViewControllers in your NSTabViewController.

Using Swift only:

Here’s the code for implementing the same using code only:

import Cocoa

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.toolbar?.displayMode = .iconOnly
        
        self.tabBar?.setSelected(true, forSegment: 0)
        
        self.tabBarController = self.window?.contentViewController as? NSTabViewController
        self.tabBarController?.tabView.tabViewType = .noTabsNoBorder
        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
        self.tabBarController?.selectedTabViewItemIndex = segmentedControl.selectedSegment
    }
    
}

Result

Relevant Documentation

Feedback

I still have a lot to learn in macOS development, so if you spot any errors or have any suggestion on improving this tutorial, please contact me on Twitter.