I have the following code: (can be copy-pasted to New macOS project)
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp])
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
statusBarItem.menu = statusBarMenu
}
@objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
} else {
print("Left click!")
}
}
@objc func orderAnApple() {
print("Ordering a apple!")
}
@objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
Actual behaviour: Menu opens on both left and right click, statusBarButtonClicked is not triggered.
After removing this line:
statusBarItem.menu = statusBarMenu
statusBarButtonClicked triggers on left click, menu doesn't show up (as expected)
Desired behaviour: Menu opens on right click, on left click menu doesn't open, action is triggered.
How do I achieve it?
I managed to achieve desired behavior with help of @red_menace comment:
import Cocoa
import SwiftUI
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate {
var statusBarItem: NSStatusItem!
var menu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(
withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
// Setting action
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
let statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
// Setting menu
menu = statusBarMenu
}
@objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
statusBarItem.popUpMenu(menu)
} else {
print("Left click!")
}
}
@objc func orderAnApple() {
print("Ordering a apple!")
}
@objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}
But Xcode says that openMenu
func is deprecated in 10.14 and tells me to Use the menu property instead
. Is there I way to achieve desired behaviour with new API?
The usual way a to show a menu is to assign a menu to the status item, where it will be shown when the status item button is clicked. Since popUpMenu
is deprecated, another way is needed to show the menu under different conditions. If you want the right click to use an actual status item menu instead of just showing a contextual menu at the status item location, the status item menu property can be kept nil until you want to show it.
I've adapted your code to keep the statusBarItem
and statusBarMenu
references separate, only adding the menu to the status item in the clicked action method. In the action method, once the menu is added, a normal click is performed on the status button to drop the menu. Since the status item will then always show its menu when the button is clicked, an NSMenuDelegate method is added to set the menu property to nil
when the menu is closed, restoring the original operation:
import Cocoa
@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate, NSMenuDelegate {
// keep status item and menu separate
var statusBarItem: NSStatusItem!
var statusBarMenu: NSMenu!
func applicationDidFinishLaunching(_ aNotification: Notification) {
let statusBar = NSStatusBar.system
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.squareLength)
statusBarItem.button?.title = "🍎"
statusBarItem.button?.action = #selector(self.statusBarButtonClicked(sender:))
statusBarItem.button?.sendAction(on: [.leftMouseUp, .rightMouseUp])
statusBarMenu = NSMenu(title: "Status Bar Menu")
statusBarMenu.delegate = self
statusBarMenu.addItem(
withTitle: "Order an apple",
action: #selector(AppDelegate.orderAnApple),
keyEquivalent: "")
statusBarMenu.addItem(
withTitle: "Cancel apple order",
action: #selector(AppDelegate.cancelAppleOrder),
keyEquivalent: "")
}
@objc func statusBarButtonClicked(sender: NSStatusBarButton) {
let event = NSApp.currentEvent!
if event.type == NSEvent.EventType.rightMouseUp {
print("Right click!")
statusBarItem.menu = statusBarMenu // add menu to button...
statusBarItem.button?.performClick(nil) // ...and click
} else {
print("Left click!")
}
}
@objc func menuDidClose(_ menu: NSMenu) {
statusBarItem.menu = nil // remove menu so button works as before
}
@objc func orderAnApple() {
print("Ordering a apple!")
}
@objc func cancelAppleOrder() {
print("Canceling your order :(")
}
}