Chapters

Hide chapters

Catalyst by Tutorials

Second Edition · iOS 14 · Swift 5.3 · Xcode 12.2

12. Barista Training: The Touch Bar
Written by Marin Bencevic

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Heads up... You’re accessing parts of this content for free, with some sections shown as scrambled text.

Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now

You’re just one final step away from becoming a true barista master! The one last remaining bar on your journey is… the Touch Bar. Whether or not you love the Touch Bar or think it’s a gimmick, many Mac users use it heavily. Because of this, supporting the Touch Bar is an important step in making your Catalyst app feel completely at home on macOS.

In this chapter, you’ll expand the app you’ve been working on to add a few useful items to the Touch Bar. You’ll learn about positioning those items and how to allow your users to customize them.

Before you get started, though, let’s talk a little about how the Touch Bar works under the hood.

Understanding the Touch Bar

While using your Mac, the Touch Bar is continuously changing depending on what’s active on the screen. Similarly to the menu bar, the Touch Bar uses the responder chain to determine which items to present. Take a look at Chapter 10, “Barista Training: Menu Bar” to learn more about the responder chain.

The gist is that each view controller and view is a responder, and one of them is the first responder, which is the currently active view. The responder chain works like a tree, going upwards from the first responder all the way to the root window of your app.

Each item in the responder chain can say “Here are the items I want in the Touch Bar.” When the first responder changes, the Touch Bar goes up the chain, picking up items as it goes. Of course, not all of these items fit on the Touch Bar, so the Touch Bar prioritizes items closer to the first responder. The ones at the back don’t get shown, and wait patiently for their turn to shine.

However, the ones in the back don’t necessarily have to take back stage! If you think an item is more or less important, you can set its priority to a higher or lower value. The Touch Bar will take this into account when ordering the items.

The responders suggest their items to the Touch Bar by overriding makeTouchBar. That method returns an NSTouchBar object. Don’t let the naming confuse you: The Touch Bar — the physical bar — displays multiple instances of NSTouchBar. In the following screenshot you’ll see four distinct NSTouchBar instances shown on the Touch Bar:

This is the Touch Bar of the Notes app. Bar 1 and 4 are system bars and they’re always there. Bar 3 is the bar of an active text field, which is currently the first responder. Bar 3 bullied through and hid some items from Bar 2 because Bar 2 is deeper in the responder chain.

Note: Since the Touch Bar is only available on macOS, NSTouchBar and related APIs are lifted directly from macOS and included in Catalyst, which explains the NS prefix. This means that already existing macOS-specific Touch Bar documentation and tutorials are generally applicable to Catalyst apps.

Adding items

Now that we’ve, ahem, touched on some theory, you’re ready to add some new items!

#if targetEnvironment(macCatalyst)
extension NSTouchBarItem.Identifier {
  static let newEntry =
    NSTouchBarItem.Identifier(
    "com.raywenderlich.Journalyst.addEntry")
}
#endif
#if targetEnvironment(macCatalyst)
override func makeTouchBar() -> NSTouchBar? {
  let bar = NSTouchBar()
  bar.defaultItemIdentifiers = [.newEntry]
  let button = NSButtonTouchBarItem(
    identifier: .newEntry,
    title: "New Entry",
    target: self,
    action: #selector(addEntry))
  bar.templateItems = [button]
  return bar
}
#endif

Implementing the delegate

To avoid this memory issue, you’ll implement NSTouchBarDelegate. Instead of setting the items directly on the bar, you will only give the bar a list of item identifiers. The bar will then ask the delegate for the item only when it’s needed. This is similar to how table views work: Cells are created on-demand instead of being loaded automatically.

let bar = NSTouchBar()
bar.delegate = self
bar.defaultItemIdentifiers = [.newEntry]
return bar
#if targetEnvironment(macCatalyst)
extension RootSplitViewController: NSTouchBarDelegate {
  func touchBar(
  	_ touchBar: NSTouchBar,
  	makeItemForIdentifier identifier: NSTouchBarItem.Identifier)
  	-> NSTouchBarItem? {

    switch identifier {
    case .newEntry:
      let button = NSButtonTouchBarItem(
        identifier: identifier,
        title: "New Entry",
        target: self,
        action: #selector(addEntry))
      return button
    default:
      return nil
    }
  }
}
#endif

Grouping items

It’s time to add three more items to the Touch Bar: “Delete,” “Next Entry” and “Previous Entry.” Because all three of these items relate to the currently selected entry, you’ll put all of them in a single group item instead of adding them individually.

static let entryOptions =
  NSTouchBarItem.Identifier(
  "com.raywenderlich.journalyst.entryOptions")
bar.defaultItemIdentifiers = [.newEntry, .entryOptions]
case .entryOptions:
  let next = NSButtonTouchBarItem(
    identifier: .init(identifier.rawValue + ".next"),
    title: "Next Entry",
    target: self,
    action: #selector(goToNext))
  let previous = NSButtonTouchBarItem(
    identifier: .init(identifier.rawValue + ".previous"),
    title: "Previous Entry",
    target: self,
    action: #selector(goToPrevious))
  let delete = NSButtonTouchBarItem(
    identifier: .init(identifier.rawValue + ".delete"),
    title: "Delete",
    target: self,
    action: #selector(removeEntry))
let spacer = NSTouchBarItem(identifier: .fixedSpaceLarge)
let group = NSGroupTouchBarItem(
  identifier: identifier,
  items: [spacer, next, previous, spacer, delete])
return group

bar.principalItemIdentifier = .entryOptions

Customizing the Touch Bar

If there’s one thing nerds like us enjoy, it’s customization options. Apple clearly had this in mind when they created the Touch Bar, as they added app-specific Touch Bar customization. As a developer, it’s relatively easy to add support for this.

#if targetEnvironment(macCatalyst)
NSTouchBar.isAutomaticCustomizeTouchBarMenuItemEnabled = true
#endif
extension NSTouchBar.CustomizationIdentifier {
  static let journalyst = NSTouchBar.CustomizationIdentifier(
    "com.raywenderlich.journalyst.main")
}
bar.customizationIdentifier = .journalyst
bar.customizationAllowedItemIdentifiers = [.newEntry, .entryOptions]
button.customizationLabel = "Add a new entry"
group.customizationLabel = "Entry Options"

Key points

  • The Touch Bar is made of NSTouchBar instances.
  • The Touch Bar uses the responder chain to determine which items to show.
  • Each view and view controller can add items to the Touch Bar by overriding makeTouchBar and returning an NSTouchBar.
  • Use templateItems only for lightweight items.
  • For other items, implement NSTouchBarDelegate.
  • Allow customization by enabling the customization menu item, making items customizable, and adding customization labels to the items.

Where to go from here?

To see some other Touch Bar items in action, check out the NSTouchBar tutorial written by Andy Pereira, one of the authors of this book: bit.ly/2kxqPjs.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2024 Kodeco Inc.

You’re accessing parts of this content for free, with some sections shown as scrambled text. Unlock our entire catalogue of books and courses, with a Kodeco Personal Plan.

Unlock now