Chapters

Hide chapters

Catalyst by Tutorials

Third Edition · iOS 15 · Swift 5.6 · Xcode 13.3

Section I: Making a Great iPad App

Section 1: 7 chapters
Show chapters Hide chapters

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

Heads up... You're reading this book for free, with parts of this chapter shown beyond this point as scrambled text.

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. While newer MacBooks have ditched the touch bar, there are still many Mac users with older laptops who use the Touch Bar regularly. Since Apple makes supporting the Touch Bar really easy there is no reason why you wouldn’t take this 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, you’ll learn 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 three distinct NSTouchBar instances shown on the Touch Bar:

Notes app Touch Bar.
Notes app Touch Bar.

This is the Touch Bar of the Notes app. Bar 3 is the system bar and it’s always there. Bar 1 is the bar of an active text field, which is currently the first responder. Bar 2 bullied through and hid some items from Bar 1 because Bar 1 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.yourcompany.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
New Entry item shows in the Touch Bar.
Lud Ivcdj edij ttafj ec qce Foikf Sat.

Implementing the Delegate

To avoid this memory issue, you’ll implement NSTouchBarDelegate. Instead of setting the items directly on the bar, you’ll only give the bar a list of item identifiers. The bar then asks 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
New Entry item again shows in the Touch Bar.
Toz Eqpzw usic uteoc qfixq ot hwe Xaazy Hil.

Grouping Items

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

static let entryOptions = NSTouchBarItem.Identifier(
  "com.yourcompany.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
Entry items with spacing in the Touch Bar.
Omxhj ajosm jekq lrucepj is nla Yaamb Sog.

bar.principalItemIdentifier = .entryOptions
Group item centered in the Touch Bar.
Npuiq oxav kagjucur iz kdu Tiots Baz.

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.yourcompany.journalyst.main"
  )
}
bar.customizationIdentifier = .journalyst
bar.customizationAllowedItemIdentifiers
  = [.newEntry, .entryOptions]
button.customizationLabel = "Add a new entry"
group.customizationLabel = "Entry Options"
Customization Screen.
Telbowazakuos Ydnoob.

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 here, the NSTouchBar tutorial written by Andy Pereira, one of the authors of this book.

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 reading for free, with parts of this chapter shown as scrambled text. Unlock this book, and our entire catalogue of books and videos, with a Kodeco Personal Plan.

Unlock now