Chapters

Hide chapters

macOS Apprentice

First Edition · macOS 13 · Swift 5.7 · Xcode 14.2

Section II: Building With SwiftUI

Section 2: 6 chapters
Show chapters Hide chapters

Section III: Building With AppKit

Section 3: 6 chapters
Show chapters Hide chapters

10. Adding Toolbars & Menus
Written by Sarah Reichelt

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

In the previous chapters in this section, you built a fully functional app. You can play the game, change settings and see your game statistics in charts.

Now, you’ll add more of the standard features that Mac users expect: toolbars, menus and an app icon.

By the end of this chapter, your app will be complete and you’ll see how you can export it from Xcode and get it into your Applications folder.

Toolbars

Open your app in Xcode or use the project from the starter folder in the downloads for this chapter. Run the app and look at the main window. You already have a toolbar:

Initial toolbar
Initial toolbar

This is because your view uses NavigationSplitView. This contains a sidebar and, if you drag the divider to the left of the window and let go, you can’t drag it back. The single button in the toolbar toggles the sidebar so you can always get it back.

You have no control over that toolbar item, but you can add your own items. You’ll add a Boss button that users can click to hide the fact that they’re playing your game at work! ;]

Open ContentView.swift and add this after the frame modifier:

// 1
.toolbar {
  // 2
  ToolbarItem {
    // 3
    Button {
      // action goes here
    } label: {
      // 4
      Label("Boss", systemImage: "person.circle.fill")
    }
    // tooltip goes here
  } 
}

Stepping through this:

  1. Add a toolbar to NavigationSplitView. Even though it already added one by itself, the toolbar modifier lets you add more items to it.
  2. Insert a ToolbarItem into the toolbar. This is a view type designed for this purpose.
  3. A toolbar item can contain many different types of views, but a Button is the most usual. You’ll add the button’s action — what it does when the user clicks it — later.
  4. Views in a toolbar can show text, icon or both. A Label is ideal for this as it’s a view that contains both. The first argument is the text and the second uses an image from SF Symbols.

Run the app again to check out your new toolbar item:

Toolbar item
Toolbar item

It doesn’t do anything yet, but it’s there.

Before making it work, there are a couple of options to set. By default, SwiftUI has put it on the trailing end of the toolbar, but you can override this.

Replace the ToolbarItem line with:

ToolbarItem(placement: .navigation) {

Unfortunately, the preview doesn’t show toolbars, so run the app again to see the difference:

Toolbar placement
Toolbar placement

The options you’ll use most often are navigation, primaryAction and principal. Try each to see where they place your button, then switch the placement to automatic to let SwiftUI work out the most appropriate placement.

The final addition to make is to add a tooltip so users can mouse over the toolbar item and read what it does.

Replace // tooltip goes here with:

.help("Quick, the boss is coming!")

This adds a modifier to the Button and provides a tooltip and an accessibility hint:

Toolbar button tooltip
Toolbar button tooltip

Now, it’s time to add the action.

Coding the Toolbar Button

The Boss button has to hide the game and display a blank window with an appropriate window title instead.

@Published var bossMode = false
appState.bossMode.toggle()
Lines to delete
Vifod hi pocore

// 1
Group {
  // 2
  if appState.bossMode {
    // 3
    Color.white
      // 4
      .navigationTitle("Work")
  } else {
    // 5
    NavigationSplitView {
      SidebarView(appState: appState)
    } detail: {
      GameView(appState: appState)
    }
  }
}
Boss mode
Dard lumu

Animating the Change

Now you and your users are safe if the boss wanders past, but the switch from one mode to the other is rather abrupt.

.animation(.easeInOut, value: appState.bossMode)

Menus

Every standard Mac app uses the system menu bar at the top of the screen. This menu bar has a consistent layout with standard menu items and keyboard shortcuts.

Including Preset Menu Groups

To start with, you’ll tell SwiftUI to include some preset menu groups in your app. Open SnowmanApp.swift and add this after the end of WindowGroup:

// 1
.commands {
  // 2
  SidebarCommands()
  // 3
  ToolbarCommands()
}
Expanded View menu
Ugpidjiq Gaah zabi

Customizing the Toolbar

The menu bar has disabled the Customize Toolbar… item because you haven’t set up the toolbar for customization yet. To make it editable, the toolbar, and each item in it, must have an id.

ToolbarItem(id: "boss_mode_toolbar_item", placement: .automatic) {
.toolbar(id: "content_view_tooolbar") {
Customizing the toolbar.
Duvratomitg wri xiehdov.

Adding a Custom Menu Item

The pre-built menu groups are useful and cover a lot of options, but sometimes, your app needs more. In this app, there’s an item in the File menu to create a new window. Since the data is app-wide, a new window duplicates the existing window, which isn’t useful. But users expect the Command-N shortcut to make something new, so you’ll re-purpose it to start a new game instead.

// 1
CommandGroup(replacing: .newItem) {
  // 2
  Button("New Game") {
    // 3
    appState.startNewGame()
  }
  // 4
  .keyboardShortcut("n")
}
New Game
Fan Famu

Hiding a Standard Menu Item

In the previous section, you re-purposed an existing menu item to suit your app. But what if you want to delete an item instead of replacing it?

Snowman Help
Bpulyac Cojm

// 1
CommandGroup(replacing: .help) {
  // 2
  EmptyView()
}
Truncated Help menu
Cpodzilil Kaqp buru

Inserting a Custom Menu

Sometimes, you need to add a completely new menu. You’ll add a Game menu that has a couple of items to help the user control the game.

// 1
CommandMenu("Game") {
  // 2
  Toggle("Boss Mode", isOn: $appState.bossMode)
    // 3
    .keyboardShortcut("b")

  // 4
  Button("Different Word") {
    // change word action
  }
  .keyboardShortcut("d")
}
Game menu
Seci niwe

Disabling a Menu Item

Before coding the methods to select a different word, you want to decide when this should be possible. It isn’t fair to let the player get halfway through a game and decide the word is too difficult. You only want to enable this menu item until the player makes a first guess. This lets your users change words if they don’t like the length of the word they got.

// 1
var gameHasStarted: Bool {
  // 2
  !games[gameIndex].guesses.isEmpty
}
.disabled(appState.gameHasStarted)
Disabled menu item
Carugkuq toho iciz

Choosing a Different Word

Implementing changing words requires changes to several files. First, open Game.swift and add this method:

// 1
mutating func chooseNewWord() {
  // 2
  word = getRandomWord()
}
// 1
func getDifferentWord() {
  // 2
  games[gameIndex].chooseNewWord()
}
appState.getDifferentWord()
Choosing different words.
Dqoekuwb noxlomehz lighh.

Adding an App Icon

Take a look at the app icon in the Dock. It’s a sad looking default icon that doesn’t tell you anything about the app.

Drag image into Bakery.
Qbev ovowu uyzu Sehahw.

Importing the icon.
Idcimbawc nha ofuk.

Remove iOS icon files.
Xonova aOK oqar konuy.

About box with icon
Ofuor lec nehr ifan

Exporting your Mac app

So far, you’ve always run your app from Xcode. But you’ve made such a great game that it’d be fantastic to have it running from your Applications folder.

Archived app in Organizer window.
Avgyuwuc iwq if Ucxabaqox kivsug.

App warning dialog
Iwy caccinj diegeq

Challenge

Your boss may not be convinced by a totally blank white window with no actual work showing. Take a screenshot of a real work window and use that instead. This requires several steps:

Key Points

  • Mac users expect standard apps to operate in similar ways. This includes adding toolbars to main windows and using the menu bar.
  • SwiftUI has options for including preset menu groups. Use these whenever it suits your app, as they keep the interface consistent with Apple’s guidelines.
  • Keyboard shortcuts are a good way to make your apps more usable and adding them to menu items makes it easier for users to find and learn them.
  • When you’ve finished coding, you can add an icon for your app and export it for use outside Xcode.

Where to Go From Here

You’ve done a terrific job and created a great little app. What could you do with it now?

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