Active Filters: Touch Bar

Topics

#281: Touch Bar Basics 🚥

Topics

During yesterday's MacBook Pro event, Apple announced a fantastic new piece of hardware called the Touch Bar. It's a 1085 x 30 point matte-finish Retina screen that sits above the keyboard on the new MacBook Pros. It can dynamically alter its controls and content as we work in each app on the main screen.

Today we'll look at the basics of adding Touch Bar support to a macOS app. Let's dive in.

First some pre-requisites. You'll need the latest version of Xcode (8.1 at publish time), and the latest version of macOS. Even for those running 10.12.1, this updated 10.12.1 build still needs to be installed to be able to test Touch Bars using Xcode.

After we've made sure we've got the latest versions of everything, we can launch Xcode 8.1 and select Window > Show Touch Bar to see a simulated Touch Bar on screen while we're work on our Mac.

It even floats above all other windows to make it feel a tiny bit more realistic. Neat!

Whew! With that out of the way, we can fire up Xcode and create a new macOS app.

We'll make a new WindowController for our app's initial window, and set its class in Interface Builder.

// WindowController.swift
class WindowController: NSWindowController {
  override func windowDidLoad() {
    super.windowDidLoad()

  }
}

We'll add an extension to WindowController to adopt the NSTouchBarDelegate protocol. We'll also add an @available statement to make sure it's only available on the latest macOS:

@available(OSX 10.12.1, *)
extension WindowController : NSTouchBarDelegate {
  override func makeTouchBar() -> NSTouchBar? {
    // TODO
  }

  func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
    // TODO
  }
}

Next, we need to create our Touch Bar. We'll do this with an instance of NSTouchBar.

override func makeTouchBar() -> NSTouchBar? {
  let touchBar = NSTouchBar()
  touchBar.delegate = self
  return touchBar
}

We'll use another extension to add some identifiers for the Touch Bar Items we want to appear to NSTouchBarItemIdentifier:

extension NSTouchBarItemIdentifier {
  static let launch = NSTouchBarItemIdentifier("com.magnus.spaceships.launch")
  static let hyperspace = NSTouchBarItemIdentifier("com.magnus.spaceships.hyperspace")
}

Then, we'll set them in the order we want on our touchBar's defaultItemIdentifiers property:

touchBar.defaultItemIdentifiers = [.launch, .hyperspace]

Then, we'll implement the sort of "main" function of NSTouchBarDelegate (The "cell for item at index path" of Touch Bars so-to-speak).

We'll switch on our identifiers from earlier, and return an NSTouchBarItem for each one:

func touchBar(_ touchBar: NSTouchBar, makeItemForIdentifier identifier: NSTouchBarItemIdentifier) -> NSTouchBarItem? {
  switch identifier {

  case NSTouchBarItemIdentifier.launch:
    let item = NSCustomTouchBarItem(identifier: identifier)
    item.view = NSButton(title: "🚀 Launch", target: self, action: #selector(launch))
    return item

  case NSTouchBarItemIdentifier.hyperspace:
    let item = NSCustomTouchBarItem(identifier: identifier)
    item.view = NSButton(title: "😱 Hyperspace", target: self, action: #selector(hyperspace))
    return item

  default: return nil
  }
}

Success!

Last but not least, we can allow our Touch Bar items to be customized by assigning it a customizationIdentifier:

touchBar.customizationIdentifier = NSTouchBarCustomizationIdentifier("com.magnus.spaceships")
if #available(OSX 10.12.1, *) {
  if ((NSClassFromString("NSTouchBar")) != nil) {
    NSApplication.shared().isAutomaticCustomizeTouchBarMenuItemEnabled = true
  }
}

We'll also need to define which items can be customized:

touchBar.customizationAllowedItemIdentifiers = [.launch, .hyperspace]

We can test out customization by selecting View > Customize Touch Bar... from our app's menu.

(Pro Tip: Hold Option to change the menu item to Customize Control Strip... instead, which allows us to customize the persistent NSTouchBarItems that are always available on the right side of the Touch Bar). Neat!