macOS integration
Build a menu bar extra
Expose a small set of frequent actions from the system menu bar without turning the extra into a second complete application.
The icons at the right end of the menu bar are menu bar extras. They exist for one job: giving the user something quick without making them find your window.
For our notes app, that means capturing a thought in two clicks from inside any other app.
Declare the extra
SwiftUI turned what used to be a pile of NSStatusItem code into a scene. Declare it beside the others:
MenuBarExtra("Notes", systemImage: "note.text") {
MenuBarContent()
.environmentObject(model)
}
The content is a normal view, so environment actions work inside it:
struct MenuBarContent: View {
@EnvironmentObject private var model: NotesModel
@Environment(\.openWindow) private var openWindow
var body: some View {
Button("New Note") { model.createNote() }
Divider()
Button("Open Notes") { openWindow(id: "main") }
}
}
For that last button to work, give the main scene an identifier: WindowGroup(id: "main") { … }.
Menu style or window style
By default the content renders as a menu, a plain list of commands like the Wi-Fi icon. There’s a second style that shows a full view in a floating panel:
MenuBarExtra("Notes", systemImage: "note.text") {
QuickNoteView()
.frame(width: 300, height: 200)
}
.menuBarExtraStyle(.window)
My advice: choose the menu style unless the content needs controls a menu cannot hold, like a text field for typing a quick note. The window style is heavier and behaves differently. It stays open while the user interacts, and you become responsible for making it feel dismissable.
The label still matters
Users see the icon, not the string. But VoiceOver reads the label, and it identifies the item when users rearrange extras by ⌘-dragging them. Give it a real name.
Verify
Run the app and look at the top right of the screen. The note icon should sit among the system extras.
Click it, create a note from the menu, then open the main window and confirm the note is there. Both scenes share one model, which is why we inject the same instance everywhere.
Watch for scope creep
The mistake with menu bar extras is growth. It starts as three commands. Then it grows tabs, settings, and scrolling lists, until you’ve built a second app inside a popover.
When the extra needs that much, the answer is opening the real window. Keep the extra as the shortcut, not the destination.
One more warning. If the extra becomes the app’s only interface, with the Dock icon hidden, add an explicit Quit button to its menu. Without a Dock icon, that menu is the only place the user can quit from.
Lesson completed