João Gabriel
João GabrielJune 8, 2022

Hands-on: building a Menu Bar experience with SwiftUI

We'll show you how to use the new SwiftUI MenuBarExtra API to build a menu bar app experience on macOS.
Building a Menu Bar experience with SwiftUI

WWDC22 was absolutely packed with new developer features and quality-of-life improvements in almost all areas. We've heard about many new APIs so far, but SwiftUI is probably the framework that saw the most changes, with an overhauled navigation system, a mouthful of new views, protocols, modifiers, styles and customization options and tighter integration with already-existing technologies.

It's clear that the goal now is integration, and the future for the platform couldn't be brighter.

One great example of where this shines is with window management in SwiftUI, which has completely new out-of-the-box features for managing windows on the Mac and the iPad.

For the surprise of many, this includes a new struct for creating and managing Menu Bar items in macOS Ventura: please welcome MenuBarExtra.

How it was done (before)

For years, Mac utilities have used the most out of the Menu Bar. Some even lived inside the menu bar, and were designed so they would never rely on system windows.

To instantiate a Menu Bar item in the past, you had to add a code structure similar to this to the AppDelegate:

AppDelegate.swift
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
    private var statusBar: NSStatusBar
    private var statusItem: NSStatusItem
 
    var view: NSView {
        let view = NSHostingView(rootView: ContentView())
		/// You had to set an explicit frame
        view.frame = CGRect(x: 0, y: 0, width: 275, height: 125)
        return view
    }
 
    func applicationDidFinishLaunching(_ aNotification: Notification) {
        statusBar = .system
	    /// You had to set an explicit length!
        statusItem = statusBar.statusItem(withLength: 24.0)
		/// Also, let's not talk about configuring the item button's icon
        let menu = NSMenu()
        let menuItem = NSMenuItem()
		/// Add SwiftUI view wrapped in NSView wrapped in NSMenu
        menuItem.view = view
        menu.addItem(menuItem)
        statusItem.menu = menu
    }
}

I've been there.

And it's needless to say, this didn't fit the SwiftUI lifecycle or the declarative paradigm at all. Having a separate AppDelegate also meant manually implementing your own code for managing windows and any other aspects.


In the What's new in SwiftUI session of WWDC22, the use of Menu Bar items was briefly mentioned. Even though it's officially called an extra, apps that use it exclusively were also given a shout out. You can check all of it here, at around 12min30s in.


Now, with SwiftUI 4.0, all you need to do to run a Menu Bar application is to simply add the following:

MenuBarApp.swift
@main
struct MenuBarApp: App {
    var body: some Scene {
    /// Any of your other scenes
 
    MenuBarExtra("Menu Bar item", systemImage: "circle.fill") {
    /// Your shiny views
    }.menuBarExtraStyle(WindowMenuBarExtraStyle())
    }
}

MenuBarExtra conforms to Scene – so it lives right with your other scenes, such as windows – and is filled with initializer options. You can set a name, a system image, a custom image and even a boolean binding for whether or not the item is inserted in the Menu Bar.

Currently, this scene has one custom modifier: .menuBarExtraStyle(\_ style:), which defines what type of content style the Menu Bar item adopts. There are currently two explicit styles:

The menu style
MenuBarApp.swift
MenuBarExtra("Menu Bar", systemImage: "circle.fill") {
    Text("Hello, world!")
    Image(systemName: "globe")
    Rectangle()
}.menuBarExtraStyle(.menu)

This style behaves just like a context menu, and all the the views in the hierarchy are stacked vertically, as long as they can be displayed inline – such as SF Symbols, labels and buttons. The rectangle was omitted in this hierarchy because there's no way to display it inline.

The menu is quicker and easier to set up, and it can also show keyboard shortcuts and such. A neat difference is that a menu item stays lit whenever the menu itself is being displayed – more like a toggle, and not a button.

Window

The window style
MenuBarApp.swift
MenuBarExtra("Menu Bar", systemImage: "circle.fill") {
    Text("Hello, world!")
    Image(systemName: "globe")
    Rectangle()
}.menuBarExtraStyle(.window)

This style behaves just like a window, and all the the views in the hierarchy are displayed in whatever layout you want, just like in any SwiftUI view. In the code above, the views are being implicitly aligned vertically.

This type of menu will make thoughtful design pop and shine, although it might require the same kind of attention when developing regular interfaces. We can wait to see how many apps will come out with all these new technologies.

The menu bar item itself, in this style, works more like a button that presents or hides the window.


The menu bar also works with any other modifiers for SwiftUI scenes that already exist, as well as those new in the beta. To hide the app icon in the dock while running the app in the Menu Bar, Apple recommends setting LSUIElement in Info.plist to true.

You can check the full documentation here.


This was a quick hands-on tutorial with MenuBarExtra in macOS Ventura and SwiftUI 4.0. This is only the tip of the iceberg, so make sure to watch the Keynote, for the main new OS features, and the State of the Union, for the main new developer features.

Also, to make the most out of this WWDC, don't miss all the sessions and challenges being shared on the official page.

Thank you for reading through here! We've got a lot more to cover during the week, so stay tuned, and I'll see you next time.