Chapters

Hide chapters

macOS by Tutorials

First Edition · macOS 12 · Swift 5.5 · Xcode 13

Section I: Your First App: On This Day

Section 1: 6 chapters
Show chapters Hide chapters

3. Adding Menus & Toolbars
Written by Sarah Reichelt

In Chapter 2, “Working With Windows”, you built a macOS app with support for multiple windows, a sidebar and a details pane. You connected it to the API using the data models you designed in the first chapter.

Now it’s time to take another step towards a real Mac app by adding menus and toolbars. Mac users expect to be able to perform nearly every function in the app via a menu, preferably with a keyboard shortcut. Toolbars allow easy access to more window-specific controls.

You’re going to continue with the app you built in the last chapter and learn how to add menus and different types of menu items, as well as how to add a toolbar to the windows.

What Is a Menu?

Menu is the term applied to any user interface element that expands to show a selection of choices, but the implementation of a menu is very platform dependent. In SwiftUI for macOS, there are three ways to show a menu:

  1. With a Menu view, which allows you to insert a clickable menu anywhere in your app’s user interface.
  2. Using contextMenu to pop up a menu when a user right-clicks another UI element.
  3. Via the Mac’s system-wide menu bar.

While the first two options will look different on a Mac than in an iOS app, using them is no different. But the third option, the fixed menu bar that appears at the top of your Mac’s screen, has no iOS equivalent. It contains a standard set of menus and menu items that appear in almost all apps, and they follow a keyboard shortcut convention that users come to know. In this chapter, you’ll learn about customizing the Mac menu bar for your app by adding app-specific menus and menu items.

Setting up Menu Commands

Open your project from the last chapter or download the materials for this chapter and open the starter project.

Your app already has a default set of menus, but to change these, you need to add a commands modifier to the WindowGroup in OnThisDayApp.swift. You can insert all your menu code there, but it makes for more maintainable code if you take it out into its own file.

Create a new Swift file — not a SwiftUI View file — called Menus.swift and add this to it:

// 1
import SwiftUI

// 2
struct Menus: Commands {
   var body: some Commands {
    // 3
    EmptyCommands()
  }
}

What’s all this?

  1. Commands is a SwiftUI protocol, so you need to import SwiftUI.
  2. Both Menus and its body must conform to Commands so that SwiftUI recognizes them as menus and menu items.
  3. Because body has to return something, you use one of the pre-built menu sets — in this case, one that does nothing.

Next, you need to connect Menus to your app’s WindowGroup. In OnThisDayApp.swift, add this modifier to WindowGroup:

.commands {
  Menus()
}

This tells SwiftUI to attach your new Menus to the menu bar. As you add to Menus, this will apply the new menus and menu items automatically, but OnThisDayApp.swift remains uncluttered and easy to read.

Using Pre-built Menu Groups

One of the easiest ways to add menu items is to include some of the pre-built menu groups Apple has supplied. Not all apps need all these groups, but this method allows you to insert consistent groups of menu items if they’re relevant.

You’ve already added and tested EmptyCommands but here are the others you can include, which you’ll probably find slightly more useful. :]

  • SidebarCommands
  • ToolbarCommands
  • TextEditingCommands
  • TextFormattingCommands
  • ImportFromDevicesCommands

Note: Check out Xcode’s documentation for CommandGroup and scroll to Standard Command Groups to check if Apple has added any more.

This app doesn’t have any text editing and won’t need to import any photos or scans from nearby iOS devices, so you can ignore those choices. But, it does have a sidebar and will have a toolbar, so the first two look useful.

Why does EmptyCommands exist? Imagine you were building a menu based on some condition and you only wanted your menu to appear in certain circumstances. Since body must return something, you can use this to effectively return nothing. In ordinary SwiftUI views, you use EmptyView for the same purpose.

In Menus.swift, replace EmptyCommands() with:

SidebarCommands()

Build and run the app.

Remember in the last chapter you found that it was possible to hide the sidebar but impossible to retrieve it? The View menu now contains a Hide/Show Sidebar menu item:

Sidebar commands
Sidebar commands

Drag the vertical divider to hide the sidebar and then use this menu item to show it again. Try out the keyboard shortcut — Control-Command-S — to toggle the sidebar in and out.

You don’t have a toolbar yet, but in preparation, add the following on the next line after SidebarCommands():

ToolbarCommands()

Unlike with SwiftUI views, you don’t need to wrap sets of commands in a container.

Build and run now, and you’ll see the View menu has got another new group of items. Since you don’t have a toolbar yet, your app has disabled them automatically, but they’re ready for when you add the toolbar:

Toolbar commands
Toolbar commands

With these pre-built command groups, the system decides where to put them, and you can’t adjust that. But, you don’t have to worry about connecting them to actions, setting keyboard shortcuts or localizing them. And they’ll always follow Apple’s guidelines, so you should prefer using these over building your own, if they’re appropriate to your app.

Inserting a Menu Item

Now that you know how to apply a pre-built set of menu items, it’s time to look into adding your own menu item to an existing menu. For this, you’re going to add a link to the API site in the Help menu.

To add menu items, you wrap them in a CommandGroup. This is similar to the pre-built menu commands; each of them is also a CommandGroup.

When you initialize a CommandGroup, you must tell it where to put its items. In Menus.swift, underneath ToolbarCommands, type CommandGroup(. After you’ve typed the opening parenthesis, you’ll see the auto-complete suggestions showing your three options:

CommandGroup auto-complete
CommandGroup auto-complete

As you can see, you get to place your group after, before or replacing something of type CommandGroupPlacement. If you look up the documentation for CommandGroupPlacement, you’ll see a list of placements for standard menu items that you can use to position your new items.

Since you want to place your new item in the Help menu, positioning it before the help CommandGroupPlacement, looks like the way to go.

Replace your half-typed CommandGroup line with:

CommandGroup(before: .help) {
}

Now you have your own CommandGroup but what are you going to put into it? A SwiftUI view! There are several view types that you can use, but the most common is a Button.

Put this code inside your CommandGroup:

// 1
Button("ZenQuotes.io web site") {
  // 2
  showAPIWebSite()
}
// 3
.keyboardShortcut("/", modifiers: .command)

Stepping through these lines:

  1. Create a Button with a title.
  2. Add an action that will call a method.
  3. Assign a keyboard shortcut.

This causes an error because you haven’t defined showAPIWebSite() yet, so insert this method into Menus after body:

func showAPIWebSite() {
  // 1
  let address = "https://today.zenquotes.io"
  guard let url = URL(string: address) else {
    fatalError("Invalid address")
  }
  // 2
  NSWorkspace.shared.open(url)
}

Some of this will be familiar but the last line may be new to you:

  1. Try to create a URL from the ZenQuotes.io web address. If this fails, it’ll be due to an error when typing the address, so catch it during development with a fatalError.
  2. NSWorkspace is an AppKit class that gives access to other apps and system services. Each app automatically gets a shared instance of NSWorkspace it can use. Its open method will open a supplied URL in the system’s default app for that URL type. In this case, it’ll be the default browser.

Build and run the app and open the Help menu to see your new menu item. Select it to open the URL in your browser. Go back to your app and press Command-/ to return to the page in your browser via the keyboard shortcut.

Help menu item
Help menu item

You’ve only added one control to this new CommandGroup but you could have added up to ten.

Creating a New Menu

To insert a menu item, you used CommandGroup but to add a completely new menu, you’ll use CommandMenu. Within your CommandMenu you can add views for the menu items, arranged as you like, but you can’t set the position of your menu in the menu bar.

You’re going to add a Display menu that controls various aspects of the app’s appearance. Inside Menus.swift, add this to body after your CommandGroup:

CommandMenu("Display") {
  // display menu items go here
}

Build and run and you’ll see a Display menu between the View and Window menus, but since it has no menu items yet, it’s a bit boring.

When you added the menu item to the Help menu, you used a Button. That’s going to be the most common view type for a menu item, but you can use others. One that works particularly well is Toggle. You’ve probably seen menu items in other apps that have a check mark beside them that gets turned off and on as you select the menu item — look in Xcode’s Editor menu for some examples. This is a perfect use case for a Toggle.

Before you can set up a Toggle view, you need a property to bind to it. In this case, you’re going to add a setting that shows or hides the number of each type of event in the sidebar. And since you want this setting to persist between app launches, you’re going to use @AppStorage.

Saving App Settings

If you’ve already used @AppStorage, then you can skip this section, but for those who haven’t encountered it before, @AppStorage is a property wrapper that gives easy access to UserDefaults.

Every app, in both macOS and iOS, stores its internal settings in UserDefaults. For a macOS app, this is things like window sizes and positions, sidebar visibility and width, toolbar settings and so on. You can use this system to store small chunks of data like preferences, but working with UserDefaults is not easy. You have to manually read and write the data. You must provide default settings for when the user hasn’t made any choices. And you have to try to keep the display in sync with the settings.

With SwiftUI and @AppStorage, this becomes much simpler. You initialize a property using the @AppStorage property wrapper, giving it a default value. SwiftUI will handle saving and retrieving its value and, whenever the value changes, your views will automatically update to match.

You can only store certain data types in @AppStorageInt, Bool, String, Float, Double, Data, or URL — but this covers many possibilities. You can also store enumeration values if they conform to one of these types.

Adding a Toggle

In Menus.swift, outside body but inside the struct, insert this:

@AppStorage("showTotals") var showTotals = true

This line does a lot of work! It declares a Boolean property called showTotals and sets it to true by default. It wraps this property in the @AppStorage property wrapper, assigning it the UserDefaults key of showTotals.

Now that you have a property, you can add your Toggle. Replace // display menu items go here with this:

// 1
Toggle(isOn: $showTotals) {
  // 2
  Text("Show Totals")
}
// 3
.keyboardShortcut("t", modifiers: .command)

// more menu items go here

What does this snippet do?

  1. Insert a Toggle view with its isOn value bound to showTotals.
  2. Give the Toggle a text label.
  3. Add a keyboard shortcut of Command-T.

You can set your shortcuts using uppercase or lowercase letters. The menus will always show them in uppercase, but you don’t need to hold down Shift to trigger them.

SwiftUI won’t let you overwrite a standard shortcut. If you try to use something like Command-C, the menu won’t show it or respond to it. If you apply your own shortcut to more than one menu item, they’ll all appear but only one of them will work.

If you want to use more than one modifier in your keyboard shortcut, you supply an array of them, like this:

.keyboardShortcut("t", modifiers: [.command, .shift, .option])

And if you want every possible modifier — Shift-Command-Option-Control — you can use .all.

Build and run the app and admire your new Display menu. Pop it down and confirm that the toggle has checked Show Totals:

Toggle menu item
Toggle menu item

Select the menu item either with your mouse pointer or with Command-T. Check the menu again and confirm that Show Totals is now unchecked. Quit the app, restart it and you’ll see that the menu item is still unchecked because the @AppStorage property wrapper saved it.

You’re editing and storing this setting, but it isn’t changing any part of your app’s display yet. To fix this, go to SidebarView.swift and add these declarations:

@EnvironmentObject var appState: AppState
@AppStorage("showTotals") var showTotals = true

SidebarView can now access appState and showTotals. It may seem wrong to declare showTotals more than once, but you’re only declaring a link to the value in the user’s settings.

With these in place, add this modifier to the Text view inside the ForEach:

.badge(
  showTotals
  ? appState.countFor(eventType: type)
  : 0)

badge attaches a number to the rows in a list. If the number is set to zero, no badge appears. This code uses the ternary operator to check showTotals. If it’s true, this queries appState for the count for the current event type. If it’s false, set the number to zero.

Build and run the app. Toggle the Show Totals menu item and see the numbers in the sidebar hide and show:

Sidebar badges
Sidebar badges

The SidebarView preview is not going to work any more because it doesn’t have access to the @EnvironmentObject. Using AppState in a preview may hit the API usage limits, so delete SidebarView_Previews now.

Using a Picker

You’ve used a Button and a Toggle in your menus, but another SwiftUI view that can be useful as a menu item is a Picker. As is the case with all SwiftUI views, the system will customize the appearance of the view to suit its purpose, so a Picker in a menu isn’t going to look like a Picker in a window. It’ll have a menu item that pops out a submenu showing the possible options. The selected option in the submenu will have a checkmark beside it.

Like iOS devices, Macs support light and dark mode as well as an automatic mode which swaps between the two depending on the time of day. If you do nothing, your app will go along with the user’s choice made in System Preferences ▸ General but it’s also a good idea to give your users a per-app choice for this.

Setting up DisplayMode

Before you can add a way to set the display mode for your app, you need to set up an enumeration to hold the options and a method to change the app’s appearance.

In the Models group, create a new Swift file called DisplayMode.swift and replace its code with this:

// 1
import SwiftUI

// 2
enum DisplayMode: String, CaseIterable {
  // 3
  case light = "Light"
  case dark = "Dark"
  case auto = "Auto"
}

Going through this:

  1. Import the SwiftUI library because this enumeration is going to use @AppStorage.
  2. Set the enumeration’s rawValue type to String and mark it as CaseIterable so you can loop over the cases.
  3. List the three options, with the raw values set to what will appear in the menu.

That sets up the enumeration itself, but now you need a way to change the appearance of your app and this requires a dip into AppKit. Just like iOS apps can use SwiftUI, UIKit or a mixture of the two, macOS apps can use SwiftUI and AppKit.

Add this method to the enumeration:

// 1
static func changeDisplayMode(to mode: DisplayMode) {
  // 2
  @AppStorage("displayMode") var displayMode = DisplayMode.auto

  // 3
  displayMode = mode

  // 4
  switch mode {
  case .light:
    // 5
    NSApp.appearance = NSAppearance(named: .aqua)
  case .dark:
    NSApp.appearance = NSAppearance(named: .darkAqua)
  case .auto:
    // 6
    NSApp.appearance = nil
  }
}

There’s quite a lot happening here:

  1. This is a static method, so it’s a method on the enumeration itself, not on any of its cases.
  2. Use @AppStorage for the selected value.
  3. Store the new setting.
  4. Use switch to work out which mode the user selected.
  5. NSApp is shorthand for NSApplication.shared, and like UIApplication.shared, gives access to the running application. appearance is the app property that dictates the appearance of the app’s windows. There are two main appearance names: aqua for light mode and darkAqua for dark mode.
  6. If you set the app’s appearance to nil, it uses whatever you selected in System Preferences.

Applying the Display Mode

When the app first starts and whenever this setting changes, you need to call DisplayMode.changeDisplayMode(to:) to apply the selection. Since this must happen as the app boots, putting it in OnThisDayApp.swift is a good idea.

First, add the @AppStorage definition at the top of OnThisDayApp:

@AppStorage("displayMode") var displayMode = DisplayMode.auto

Next, apply these two modifiers to ContentView, after its environmentObject modifier:

// 1
.onAppear {
  DisplayMode.changeDisplayMode(to: displayMode)
}
// 2
.onChange(of: displayMode) { newValue in
  DisplayMode.changeDisplayMode(to: newValue)
}

These cover the use cases discussed:

  1. When the app first starts, check the stored setting for displayMode and apply it to the app.
  2. Whenever displayMode changes, apply the new setting.

Expanding the Menu

Finally, you have everything set up so that you can add this to the menu.

In Menus.swift, add the stored property to Menus:

@AppStorage("displayMode") var displayMode = DisplayMode.auto

Next, replace // more menu items go here with this:

// 1
Divider()

// 2
Picker("Appearance", selection: $displayMode) {
  // 3
  ForEach(DisplayMode.allCases, id: \.self) {
    // 4
    Text($0.rawValue)
      .tag($0)
  }
}

There’s quite a lot going on here, but taking it bit by bit:

  1. Use Divider to get a menu separator. It’s not really necessary in this very short menu, but you need to get in the habit of breaking up long menus into related groups.
  2. Add a Picker with a title and with its selection bound to the @AppStorage property.
  3. Set the contents of the picker by looping through the cases in DisplayMode.
  4. For each item, use its rawValue as the title and its actual value as the tag. displayMode is set to the tag when the user selects an option.

Build and run the app to see the results of your hard work:

Display mode menu
Display mode menu

Change the settings and see your windows all change to match. Choose a different setting to your usual, quit the app and restart it to confirm that your app has saved and re-applied your selection.

Other Possibilities

You’ve just used a Picker to add a sub-menu. The advantage of this is that it gives you the checkmark showing the selected option. The disadvantage is that it doesn’t allow for keyboard shortcuts. So what else could you have used?

A set of three buttons would allow for keyboard shortcuts, but not the checkmark. You could have set each option up like this:

Button("Light") {
  displayMode = .light
}
.keyboardShortcut("L", modifiers: .command)

These would appear in the main Display menu by default, but you can group items into a sub-menu like this:

Menu("Appearance") {
  Button("Light") {
    displayMode = .light
  }
  .keyboardShortcut("L", modifiers: .command)

  // other buttons here
}

You can see a sample implementation of this commented out in Menus.swift in the final and challenge project folders.

None of these options is wrong; it depends on the app and the menu items. In this case, it’s easy to confuse the auto display mode with whatever would be valid at this time of day, so the checkmark is more valuable than the keyboard shortcuts. But you now have the knowledge to apply whatever type of menu and menu items your app requires.

Adding a Toolbar

So far in this chapter, you’ve concentrated on the menu bar and added app-wide controls. For user interface elements at the window level, macOS apps use a toolbar, so adding that is your next task.

If you’re familiar with toolbars in iOS apps, you’ll see they’re set up much the same in macOS apps, but they’re more common in the latter. macOS always puts toolbars at the top of the window, and you’ll see some different options for positioning toolbar items.

You applied your menus to the main app WindowGroup but you’ll attach the toolbar to a window’s view. In ContentView.swift, add this after the navigationTitle modifier:

.toolbar {
  // toolbar items go here
}

This sets ContentView up to have a toolbar, but there’s nothing in it yet. Like you did with the menus, you’re going to add a new file to hold the toolbar content. And like you did with the views, you’re going to make a new group in the Project navigator to hold this sort of file.

Select Menus.swift in the Project navigator, right-click and choose New Group from Selection. Call the new group Controls and then add a new Swift file to that group, naming it Toolbar.swift.

Controls Group
Controls Group

Replace the contents of your new file with:

// 1
import SwiftUI

// 2
struct Toolbar: ToolbarContent {
  var body: some ToolbarContent {
    // 3
    ToolbarItem(placement: .navigation) {
      // 4
      Button {
        // button action
      } label: {
        // 5
        Image(systemName: "sidebar.left")
      }
      // 6
      .help("Toggle Sidebar")
    }
  }
}

How does this make a toolbar?

  1. Import the SwiftUI library to support toolbars.
  2. Conform both Toolbar and body to ToolbarContent to mark them as usable in a toolbar.
  3. Add a ToolbarItem, placing it in the navigation position.
  4. Inside the ToolbarItem create a button.
  5. Use an SF Symbol as the icon for the button.
  6. Add a tooltip and accessibility description to the button.

To make this content appear, go back to where you added the toolbar modifier to ContentView in ContentView.swift and replace the comment with:

Toolbar()

Build and run the app so that you can admire your new toolbar even though it doesn’t do anything yet:

Toolbar item
Toolbar item

Check out the View menu. The Toggle Toolbar item is now enabled and functional.

When adding items to a toolbar, you can use ToolbarItem or ToolbarItemGroup. As you would expect, ToolbarItemGroup is for when you want to add a related set of controls. Most of the controls you add will be buttons, but you can use pickers, sliders, toggles, menus, even text fields.

Positioning an item uses ToolbarItemPlacement. These vary between operating systems, but here are the main ones you’re likely to use in a Mac app:

  • navigation puts the item at the leading edge, before the window title.
  • principal shows the item in the center of the window.
  • primaryAction pushes the item to the trailing edge.
  • automatic lets the system work out the best position.

Now that you have a button in your toolbar, you’ll want to add an action. As you’ve probably guessed, this will provide a second mechanism for handling the sidebar problem.

Add this method to Toolbar in Toolbar.swift:

func toggleSidebar() {
  // 1
  NSApp.keyWindow?
    // 2
    .contentViewController?
    // 3
    .tryToPerform(
      // 4
      #selector(NSSplitViewController.toggleSidebar(_:)),
      with: nil)
}

This looks weird! What’s it doing? SwiftUI doesn’t have a method for handling the sidebar, so you need to use AppKit again.

  1. NSApp.keyWindow gets the frontmost window in the application.
  2. If that works, contentViewController gets the view controller for the content in that window.
  3. Once you have a view controller, you can try to call a method on it.
  4. SwiftUI’s NavigationView is basically an AppKit NSSplitViewController, and that controller has a method to toggle the sidebar.

So this isn’t a pretty method, but it demonstrates how SwiftUI is an overlay on top of AppKit and that you can dig down into AppKit if you need to.

In body set the button’s action to:

toggleSidebar()

Now you can build and run the app and use this toolbar button to toggle the sidebar:

Sidebar hidden by toolbar
Sidebar hidden by toolbar

Searching the Grid

You’ve added an item to the toolbar manually, and you’ll add more in the next chapter, but there is another toolbar feature that SwiftUI can add automatically. Wouldn’t it be useful to be able to search the events and show only a subset of cards in the grid?

Adding search functionality to a SwiftUI view has become much easier in macOS 12 and iOS 15.

In ContentView.swift, add this property to hold any text that a user enters into the search field:

@State private var searchText = ""

Next, add a searchable modifier to NavigationView after the toolbar modifier. The modifier’s text is bound to the property you just created:

.searchable(text: $searchText)

Finally, replace the events computed property with this one which passes the search string over to appState:

var events: [Event] {
  appState.dataFor(eventType: eventType, searchText: searchText)
}

And that’s all you need to do! The searchable modifier adds a search text field to the toolbar, bound to your searchText property. If appState.dataFor() gets a string in its optional searchText parameter, it uses this to limit the events returned. If your app didn’t already have a toolbar, this would add one for you.

Build and run the app and type something into your new search field:

Searching
Searching

Open a second window and search for something different. Change the event type and the search automatically applies to the new set of events.

Making Your Toolbar Customizable

You may have noticed that the View menu has an item called Customize Toolbar…, but it’s not active. You’re going to learn how to activate it, but right now, there is a bug in SwiftUI so that if you put a search field in your toolbar, any edits you make to the toolbar items don’t stick. Hopefully, Apple will have fixed this bug by the time you read this book, but if not, file this information away for future reference.

To make a toolbar editable, the toolbar itself and each ToolbarItem must have an id property.

In ContentView.swift replace the toolbar modifier with this, ignoring the error that it will cause:

.toolbar(id: "mainToolbar") {
  Toolbar()
}

Next, open Toolbar.swift and replace your existing ToolbarItem with:

// 1
ToolbarItem(
  id: "toggleSidebar",
  placement: .navigation,
  showsByDefault: true
) {
  // 2
  Button {
    toggleSidebar()
  } label: {
    // 3
    Label("Toggle Sidebar", systemImage: "sidebar.left")
  }
  .help("Toggle Sidebar")
}

What’s changed?

  1. Define an id for the ToolbarItem and set it to show by default. The placement is the same as before.
  2. Include the same Button with the same action and tooltip.
  3. Instead of using an Image, use a Label because one of the toolbar customizations is to decide whether to show the icon, the name or both.

One more change to make: Toolbar and its body conform to ToolbarContent, but now you need to change that to CustomizableToolbarContent. Edit the structure so it starts like this:

struct Toolbar: CustomizableToolbarContent {
  var body: some CustomizableToolbarContent {

This fixes the error in ContentView.swift. Build and run the app, choose Customize Toolbar… from the View menu or by right-clicking in the toolbar and try making some changes:

Customizing the toolbar
Customizing the toolbar

You can change whether the items show their icons, text or both using the popup menu at the bottom of the sheet. You can drag the default items into the toolbar. If you drag either item out of the toolbar to remove it, it disappears in a cloud of smoke, but as soon as you click Done, it pops it straight back into place.

To confirm that this is a bug due to searchable, comment out the searchable modifier in ContentView.swift and try again. This time you’ll be able to remove the Toggle Sidebar toolbar item. Don’t forget to uncomment searchable when you’ve finished.

You still won’t be able to adjust its position because you set that specifically to navigation, but if you had more than one item and you used automatic placement, you’d be able to switch them around.

One final point: To make a toolbar customizable, every item in it must have an id. You can define a ToolbarItem with an id but not a ToolbarItemGroup, so you’ll have to stick to using ToolbarItems if you want that flexibility.

Challenges

Challenge 1: Show the number of displayed events

The badges in the sidebar show the number of events in each category, but when you do a search, the grid may show fewer events. Add a Text view to the bottom of GridView that shows the number of displayed events.

Challenge 2: Toggle this counter

Once you have this counter showing, use the stored setting for showTotals to toggle its display.

Try to do these yourself, but check out the challenge folder if you get stuck.

Key Points

  • In a macOS app, you should make all the important functions available through the menus, with keyboard shortcuts where suitable.
  • Every Mac app has access to the main menu bar. You can add preset menu groups, individual menu items or complete menus.
  • Most menu items will be buttons, but toggles and pickers are also valid as menu items.
  • @AppStorage gives you a way to store your app’s settings that will persist between app launches.
  • Toolbars provide a more window-specific way of adding controls. They can be set to allow users to customize them.
  • SwiftUI provides a searchable modifier to add a search bar to your app. You need to supply the search mechanism, but SwiftUI provides the user interface.

Where to Go From Here?

Now you have an app with data, windows, menus and a toolbar. You know a lot more about Mac menus now, as well as how toolbars work in a Mac app. And you can even search your data!

Apple’s Human Interface Guidelines are more relevant for AppKit developers, because SwiftUI removes a lot of choices and does the correct things by default. But if you’d like more information, these sections are relevant to this chapter:

In the next chapter, you’ll get to display the data in a different format, as well as expand the app to get events for selected dates instead of just for today.

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.