How to check if a modifier key is pressed when clicking on a menu bar item in macOS apps
Helm Pro yearly subscribers now get a 30% discount on RocketSim thanks to contingent pricing on the App Store.
Menu bar items are a great way to provide quick access to your macOS app’s features. In fact, there are many apps that rely solely on a menu bar item to provide all or most of their app’s functionality.
A common pattern in macOS apps is to provide different functionality or appearance based on whether the user clicked on the item with a modifier key pressed or not. Commonly, apps check if the Option
key modifier is pressed when clicking on a menu bar item and usually either show a menu instead of launching the app directly or show hidden options in the same menu.
This pattern is not only used by third-party apps like ChatGPT but is also used by Apple’s own first-party systems like the Volume menu bar item:
In this article, I will show you how you can achieve this behavior using both AppKit and SwiftUI.
AppKit
In an AppKit app, you can achieve this by implementing the menuWillOpen method from the NSMenuDelegate protocol in your app’s AppDelegate. In this method, which will get called whenever the popover is about to open, you can check if the modifier key is pressed and modify the menu accordingly:
import AppKit
final class AppDelegate: NSObject, NSApplicationDelegate {
var statusBar: NSStatusBar!
var statusBarItem: NSStatusItem!
var hiddenSetting: NSMenuItem!
func applicationDidFinishLaunching(_ notification: Notification) {
statusBar = NSStatusBar()
statusBarItem = statusBar.statusItem(withLength: NSStatusItem.variableLength)
if let button = statusBarItem.button {
button.image = NSImage(systemSymbolName: "figure.fencing", accessibilityDescription: nil)
// Hidden Item
let hiddenSetting = NSMenuItem()
hiddenSetting.title = "🤐 Hidden Setting"
hiddenSetting.target = self
hiddenSetting.action = #selector(hiddenSettingCalled)
self.hiddenSetting = hiddenSetting
// Menu
let mainMenu = NSMenu()
mainMenu.addItem(withTitle: "New Game", action: #selector(newGame), keyEquivalent: "N")
mainMenu.addItem(hiddenSetting)
mainMenu.addItem(.separator())
mainMenu.addItem(withTitle: "Quit", action: #selector(quit), keyEquivalent: "Q")
mainMenu.delegate = self
statusBarItem.menu = mainMenu
}
}
// ...
}
extension AppDelegate: NSMenuDelegate {
func menuWillOpen(_ menu: NSMenu) {
hiddenSetting.isHidden = !NSEvent.modifierFlags.contains(.option)
}
}
SwiftUI
In a SwiftUI app, you can achieve the same behavior by checking the modifier key when MenuBarExtra
’s content view is rendered. Unfortunately, the onAppear
modifier only gets called when the view is first rendered when using the default .menu
menuBarExtraStyle
. To work around this, you can use the .window
menuBarExtraStyle
, which will render the view in a window and call the onAppear
modifier every time the window is shown:
import SwiftUI
@main
struct MenuBarModifierKeyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
MenuBarExtra("Fency McFencer", systemImage: "figure.fencing") {
MenuBarView()
}
.menuBarExtraStyle(.window)
}
}
In the MenuBarView
, which will get rendered every time the menu bar item is clicked, you can check if a modifier key is pressed in the onAppear
modifier and, if it is, update a @State
property to change the view’s appearance:
import SwiftUI
struct MenuBarView: View {
@State private var showHiddenItems = false
var body: some View {
VStack {
// ...
}
.padding()
.onAppear {
showHiddenItems = NSEvent.modifierFlags.contains(.option)
}
}
}