How to create status bar icon & menu with SwiftUI like in macos Big Sur
Since this question got 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 swiftui without 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)
}
}
}