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

11. Barista Training: Toolbar
Written by Andy Pereira

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

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

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

Unlock now

Toolbars are an essential part of macOS applications. Without a doubt, NSToolbar is used so ubiquitously across so many apps that most users may overlook its presence. Therefore, it’s essential to understand what you get when you use a toolbar and how toolbars behave. By adopting NSToolbar in your app, you have access to almost two decades worth of work from the smart developers and designers at Apple.

Getting Started

Open the starter project for this chapter. Select My Mac for the active scheme, and build and run. At the moment, this project has a split view controller and can handle multiple windows:

Starter with a split view controller, handles multiple windows.
Starter with a split view controller, handles multiple windows.

Adding the Toolbar

NSToolbar is a macOS-specific control. In the past, you probably had to use macros or targets to ensure frameworks did not get imported into unsupported builds. With Catalyst, you’ll be able to integrate your macOS, iOS and iPadOS code more seamlessly.

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

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

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

Unlock now
#if targetEnvironment(macCatalyst)
// 1
if let scene = scene as? UIWindowScene,
   let titlebar = scene.titlebar {
   // 2
   let toolbar = NSToolbar(identifier: "Toolbar")
  // 3
  titlebar.toolbar = toolbar
}
#endif
Empty toolbar.
Iwmgj puidrej.

#if targetEnvironment(macCatalyst)
navigationController?.navigationBar.isHidden = true
#endif
Navigation bars removed.
Pufewidiaj jefj vunapex.

Adding Buttons

In its current state, the toolbar is providing no functionality to the app. To start adding functionality, you’ll add a few buttons.

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

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

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

Unlock now
toolbar.delegate = self
extension NSToolbarItem.Identifier {
  static let addEntry =
    NSToolbarItem.Identifier(rawValue: "AddEntry")
  static let deleteEntry =
    NSToolbarItem.Identifier(rawValue: "DeleteEntry")
  static let shareEntry =
    NSToolbarItem.Identifier(rawValue: "ShareEntry")
}

extension SceneDelegate: NSToolbarDelegate {
}
func toolbarAllowedItemIdentifiers(
  _ toolbar: NSToolbar
) -> [NSToolbarItem.Identifier] {
  return [
    .toggleSidebar,
    .addEntry,
    .deleteEntry,
    .shareEntry,
    .flexibleSpace
  ]
}
func toolbarDefaultItemIdentifiers(
  _ toolbar: NSToolbar
) -> [NSToolbarItem.Identifier] {
  return [.toggleSidebar, .addEntry, .shareEntry]
}
// 1.
func toolbar(
  _ toolbar: NSToolbar,
  itemForItemIdentifier itemIdentifier:
  NSToolbarItem.Identifier,
  willBeInsertedIntoToolbar flag: Bool
) -> NSToolbarItem? {
  var item: NSToolbarItem?
  return item
}

// 2.
@objc private func addEntry() {
}

@objc private func deleteEntry() {
}

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

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

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

Unlock now
#if targetEnvironment(macCatalyst)
private let shareItem =
  NSSharingServicePickerToolbarItem(
    itemIdentifier: .shareEntry
  )
#endif
func toolbar(
  _ toolbar: NSToolbar,
  itemForItemIdentifier itemIdentifier:
  NSToolbarItem.Identifier,
  willBeInsertedIntoToolbar flag: Bool
) -> NSToolbarItem? {

  var item: NSToolbarItem?
  switch itemIdentifier {
  // 1
  case .addEntry:
    item = NSToolbarItem(itemIdentifier: .addEntry)
    item?.image = UIImage(systemName: "plus")
    item?.label = "Add"
    item?.toolTip = "Add Entry"
    item?.target = self
    item?.action = #selector(addEntry)
  // 2  
  case .deleteEntry:
    item = NSToolbarItem(itemIdentifier: .deleteEntry)
    item?.image = UIImage(systemName: "trash")
    item?.label = "Delete"
    item?.toolTip = "Delete Entry"
    item?.target = self
    item?.action = #selector(deleteEntry)
  // 3  
  case .shareEntry:
    return shareItem
  case .toggleSidebar:
    item = NSToolbarItem(itemIdentifier: itemIdentifier)
  default:
    item = nil
  }
  return item
}
Add and Share buttons.
Adx edy Qseju fiwkisd.

Customizing the Toolbar

Toolbars don’t always have to contain a fixed set of buttons. Above, you provided a delete button without giving the user a way to see it. You can enable your toolbar to be customized by the user, and save its state between launches.

toolbar.allowsUserCustomization = true
toolbar.autosavesConfiguration = true

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

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

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

Unlock now
Customize toolbars.
Lawvipeqo wuobxudz.

Modal window to customize toolbar.
Yikav nuzhaz zo refgoziro xeahteq.

titlebar.titleVisibility = .hidden
Toolbar without a title bar.
Deizlor hujxiew e jibco tuh.

Responding to Actions

The last thing you need to do is respond to actions in the toolbar. To add items to the list, replace the empty addEntry with the following:

@objc private func addEntry() {
  guard
    let splitViewController
      = window?.rootViewController
      as? UISplitViewController,
    let navigationController
      = splitViewController.viewControllers.first
      as? UINavigationController,
    let mainTableViewController
      = navigationController.topViewController
      as? MainTableViewController
  else { return }
  DataService.shared.addEntry(Entry())
  let index = DataService.shared.allEntries.count - 1
  mainTableViewController.selectEntryAtIndex(index)
}

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

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

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

Unlock now
Entry gets added to the list.
Efbsv nayd afxuk gu tri rorb.

guard let splitViewController =
  window?.rootViewController as? UISplitViewController,
  let navigationController =
    splitViewController.viewControllers.first
    as? UINavigationController,
  let mainTableViewController =
    navigationController.topViewController
    as? MainTableViewController,
  let secondaryViewController =
    splitViewController.viewControllers.last
    as? UINavigationController,
  let entryTableViewController =
    secondaryViewController.topViewController
    as? EntryTableViewController,
  let entry = entryTableViewController.entry,
  let index = DataService.shared.allEntries
    .firstIndex(of: entry) else { return }
DataService.shared.removeEntry(atIndex: index)
mainTableViewController.selectEntryAtIndex(index)
Entry gets deleted from the list.
Ondhl toyt mukutej bnol nvo qeff.

Sharing

For the final toolbar item, Share, you’ll need to do a few more steps. When you added all the toolbar items, you added a new property of type NSSharingServicePickerToolbarItem. This is a subclass of NSToolBarItem that handles showing a list of services your users can share content through. To get this working takes a few steps.

import Combine
private var
  activityItemsConfigurationSubscriber: AnyCancellable?
activityItemsConfigurationSubscriber
  = NotificationCenter.default
  .publisher(for: .ActivityItemsConfigurationDidChange)
  .receive(on: RunLoop.main)
  .map({
    $0.userInfo?[NotificationKey.activityItemsConfiguration]
      as? UIActivityItemsConfiguration
  })
  .assign(
    to: \.activityItemsConfiguration,
    on: shareItem
  )

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

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

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

Unlock now
extension EntryTableViewController {
  private func configureActivityItems() {
    let configuration
      = UIActivityItemsConfiguration(objects: [])
    // 1.
    configuration.metadataProvider = { key in
      // 2.
      guard let shareText
        = self.shareText else { return nil }
      switch key {
      // 3.
      case .title, .messageBody:
        return shareText
      default:
        return nil
      }
    }
    // 4.
    NotificationCenter
      .default
      .post(
        name: .ActivityItemsConfigurationDidChange,
        object: self,
        userInfo: [
          NotificationKey
          .activityItemsConfiguration: configuration
        ]
      )
  }
}
override func viewDidAppear(_ animated: Bool) {
  super.viewDidAppear(animated)
  configureActivityItems()
}
func textViewDidChange(_ textView: UITextView) {
  validateState()
  configureActivityItems()
}

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

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

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

Unlock now
Share entry.
Cjeti elbzq.

Key Points

  • Use Mac style toolbars, not iOS navigation bars in macOS apps.
  • Toolbars are for the entire window, not just the specific view controller presented to the user.
  • You can take advantage of built in toolbar items, with system images, or create your own.
  • Users are used to customizing toolbars in many apps. Ensure you provide this capability, as it makes sense.

Where to Go From Here?

This chapter showed you how quick it is to implement a macOS-centric design in a way that was never so easy. While knowing how to implement your own toolbar items is important, don’t forget there are several other system-provided toolbar items provided that you can put to use as well.

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.
© 2025 Kodeco Inc.

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

Unlock now