I have included sample code below. In my MenuExtraView, I've added a dropdown Menu to access a quit option. When I render this view as the application's main window, everything works fine. But when the view is used as an item in the NSStatusItem.menu, the dropdown doesn't work despite the view being rendered correctly.
Any ideas or things I'm simply doing wrong here?
I know xcode 14 will include MenuBarExtra directly in SwiftUI, however, I'm currently targeting macos 12.
import AppKit
import SwiftUI
@main
struct MenuExtraApp: App {
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
var body: some Scene {
WindowGroup {
MenuExtraView()
// EmptyView().frame(width: .zero)
}
}
}
struct MenuExtraView: View {
var body: some View {
VStack(alignment: .leading) {
HStack(alignment: .center) {\
VStack(alignment: .leading) {
Text(verbatim: "Hello World").font(.subheadline)
}
Spacer()
Menu {
Button("Quit") {
NSApplication.shared.terminate(nil)
}.keyboardShortcut("q")
} label: {
Image(systemName: "gearshape").imageScale(.large)
}
.menuStyle(BorderlessButtonMenuStyle())
.menuIndicator(.hidden)
.fixedSize()
}.padding()
Spacer()
}
}
}
class AppDelegate: NSObject, NSApplicationDelegate {
var statusItem: NSStatusItem?
func applicationDidFinishLaunching(_ aNotification: Notification) {
let hostingView = NSHostingView(rootView: MenuExtraView())
hostingView.frame = NSRect(x: 0, y: 0, width: 350, height: 400)
let menuItem = NSMenuItem()
menuItem.view = hostingView
let menu = NSMenu()
menu.addItem(menuItem)
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem?.menu = menu
statusItem?.button?.image = NSImage(systemSymbolName: "gearshape.circle", accessibilityDescription: "Logo")
}
}
Controls, like menus and pickers with the menu style, don't work when placed in a view of an NSMenuItem. I believe only simple controls and ones with button-like behaviour do.
If you need the menu as it is, and because it would be classed as a complex view, I would recommend using a popover instead. Something like this should work:
var statusItem: NSStatusItem?
var popover: NSPopover?
func applicationDidFinishLaunching(_ aNotification: Notification) {
statusItem = NSStatusBar.system.statusItem(withLength: NSStatusItem.variableLength)
statusItem?.button?.image = NSImage(systemSymbolName: "gearshape.circle", accessibilityDescription: "Logo")
statusItem?.button?.action = #selector(showPopover)
let hostingViewController = NSHostingController(rootView: MenuExtraView())
popover = NSPopover()
popover?.contentViewController = hostingViewController
popover?.contentSize = .init(width: 350, height: 400)
popover?.behavior = .transient
}
@objc func showPopover(_ button: NSButton) {
popover?.show(relativeTo: button.bounds, of: button, preferredEdge: .minY)
}
If you just need a standard menu for your status item instead of a view, then add an item like this:
menu.addItem(withTitle: "Quit", action: #selector(NSApp.terminate), keyEquivalent: "q")