Add Navigation Buttons to NSTouchBar

Xcode and Safari sport Touch Bar items to navigate back and forth. Have a close look:

Xcode touch bar screenshot
Xcode’s Touch Bar
Safari touch bar screenshot
Safari’s Touch Bar

When you put two NSTouchBarItems next to each other, there usually is a gap. Between the navigation controls, there is a mere hairline divider, but not the regular fixed-width space.

They are not realized via NSSegmentedControl, though. Compare the navigation buttons with the system-wide controls far to the right: volume, brightness, play/pause. (I'm listening to the 1980s Pop Radio on Apple Music at the moment, in case you're curious.) The system controls are a NSSegmentedControl. They have rounded corners for the whole control, while the navigation buttons have rounded corners for every button. Also, the navigation buttons have the default button width.

To create separate navigation buttons that close together, you will need to put them in a wrapper view and reduce the buttons's vertical spacing. A NSStackView, it turns out, works just fine.

I also suspect each app is using its own set of images instead of the system provided ones since the chevrons are of different sizes in Xcode and Safari.

Here's some sample code that works:

import Cocoa

@available(OSX 10.12.2, *)
fileprivate extension NSTouchBar.CustomizationIdentifier {
    static let mainTouchBar = NSTouchBar.CustomizationIdentifier("the-touchbar")
}

@available(OSX 10.12.2, *)
fileprivate extension NSTouchBarItem.Identifier {
    static let navGroup = NSTouchBarItem.Identifier("the-touchbar.nav-group")
}

class WindowController: NSWindowController, NSTouchBarDelegate {

    override func makeTouchBar() -> NSTouchBar? {
        let touchBar = NSTouchBar()
        touchBar.delegate = self
        touchBar.customizationIdentifier = .mainTouchBar
        touchBar.defaultItemIdentifiers = [.navGroup]
        return touchBar
    }

    func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItem.Identifier) -> NSTouchBarItem? {
        switch identifier {
        case .navGroup:
            let item = NSCustomTouchBarItem(identifier: identifier)
            let leftButton = NSButton(
                image: NSImage(named: NSImage.touchBarGoBackTemplateName)!, 
                target: nil, 
                action: #selector(goBack(_:)))
            let rightButton = NSButton(
                image: NSImage(named: NSImage.touchBarGoForwardTemplateName)!, 
                target: nil, 
                action: #selector(goForward(_:)))
            let stackView = NSStackView(views: [leftButton, rightButton])
            stackView.spacing = 1
            item.view = stackView
            return item

        default:
            return nil
        }
    }

    @IBAction func goBack(_ sender: Any) { }
    @IBAction func goForward(_ sender: Any) { }
}

Here I use built-in touch bar image names, like NSImage.Name.touchBarGoForwardTemplate and touchBarGoBackTemplate.

You can also use your own vector-based @2x images. There's no benefit over using the built-in ones, really. Just in case, I whipped up two images in my favorite image editor, Acorn, and exported them as PDF.:

Touch bar screenshot with my buttons
This is what my button images look like now. (Click to download.)

Download PDFs

In case you wondered: You can take screenshots of the Touch Bar by hitting ++6.

Browse the blog archive