What is SwiftUI API for creating status bar menus?
Apple seems to use SwiftUI views in Battery & WiFi menus according to the accessibility inspector. Screenshot of a battery menu attached, also its view hierarchy.
EDIT:
Posted the solution as a separate answer.
Since this question received more attention lately, and the only reply doesn't fully solve the issue I would like to repeat the edited part of my question and mark it as resolved.
Found a way to show this in swiftui without an annoying NSPopover
. You'd need to have AppDelegate
or NSApplicationDelegateAdaptor
in case you use SwiftUI App lifecycle. Then you create NSMenu
and add NSMenuItem
that can have a custom view.
Here is the code:
let contentView = VStack {
Text("Test Text")
Spacer()
HStack {
Text("Test Text")
Text("Test Text")
}
Spacer()
Text("Test Text")
}
let view = NSHostingView(rootView: contentView)
// Don't forget to set the frame, otherwise it won't be shown.
view.frame = NSRect(x: 0, y: 0, width: 200, height: 200)
let menuItem = NSMenuItem()
menuItem.view = view
let menu = NSMenu()
menu.addItem(menuItem)
// StatusItem is stored as a class property.
self.statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
self.statusItem?.menu = menu
self.statusItem?.button?.title = "Test"
Inside the AppDelegate add the following code:
// Create the status item in the Menu bar
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
// Add a menu and a menu item
let menu = NSMenu()
let editMenuItem = NSMenuItem()
editMenuItem.title = "Edit"
menu.addItem(editMenuItem)
//Set the menu
self.statusBarItem.menu = menu
//This is the button which appears in the Status bar
if let button = self.statusBarItem.button {
button.title = "Here"
}
This will add a Button with a custom Menu to your MenuBar.
Edit - How to use SwiftUI View
As you asked, here is the answer how to use a SwiftUI View.
First create a NSPopover and then wrap your SwiftUI view inside a NSHostingController
.
var popover: NSPopover
let popover = NSPopover()
popover.contentSize = NSSize(width: 350, height: 350)
popover.behavior = .transient
popover.contentViewController = NSHostingController(rootView: contentView)
self.popover = popover
Then instead of showing a NSMenu, toggle the popover:
self.statusBarItem = NSStatusBar.system.statusItem(withLength: CGFloat(NSStatusItem.variableLength))
if let button = self.statusBarItem.button {
button.title = "Click"
button.action = #selector(showPopover(_:))
}
With following action:
@objc func showPopover(_ sender: AnyObject?) {
if let button = self.statusBarItem.button
{
if self.popover.isShown {
self.popover.performClose(sender)
} else {
self.popover.show(relativeTo: button.bounds, of: button, preferredEdge: NSRectEdge.minY)
}
}
}
In macOS 13.0+ and Xcode 14.0+, the MenuBarExtra struct allows you create a system menu bar, that is similar to NSStatusBar's icons and menus. MenuBarExtra's item will be displayed in the system menu bar when the specified binding is set to true
.
import SwiftUI
@available(macOS 13.0, *) // macOS Ventura
@main struct StatusBarApp: App {
@State private var command: String = "A"
var body: some Scene {
MenuBarExtra(command, systemImage: "\(command).circle") {
Button("Uno") { command = "A" }
.keyboardShortcut("U")
Button("Dos") { command = "B" }
.keyboardShortcut("D")
Divider()
Button("Salir") { NSApplication.shared.terminate(nil) }
.keyboardShortcut("S")
}
}
}
In Xcode's Info
tab, choose Application is agent (UIElement)
and set its value to YES
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With