Swift状态栏应用如何检测Option键按下与抬起状态
问题描述
我开发了一款以NSStatusItem形式运行的Swift Cocoa应用,会在Mac右上角状态栏展示下拉菜单。我希望用户按住Option键打开状态栏项菜单时,展示额外的「高级」菜单项。目前我可以检测到Option键的按压事件,但无法判断按键当前是按下还是抬起状态。
以下是我当前在AppDelegate.swift中编写的代码:
func applicationDidFinishLaunching(_ aNotification: Notification) { NSEvent.addGlobalMonitorForEvents(matching: NSEvent.EventTypeMask.flagsChanged, handler: keyEvent); } func keyEvent(event: NSEvent) { if let event = NSApp.currentEvent { if event.modifierFlags.contains(.option) { print("option key up/down event") } } }
这段代码可以在Option键按下、松开回弹时都打印提示信息,但问题是我无法判断任意时刻Option键的具体状态。
编辑说明:已有的同类实现方案要么逻辑过于复杂(需要添加定时器、运行循环观察者、零高度自定义视图),要么是通过创建菜单项alternates实现效果,仅支持菜单项的状态切换,无法满足本次添加额外菜单项的需求,相关实现思路可作为同类问题的参考。
实现方案
无法直接通过单次flagsChanged事件判断按键是按下还是抬起,本质原因是这类事件只回调当前触发时的修饰键状态集合,需要自行记录上一次Option键的状态,通过新旧状态比对区分动作,同时存储实时状态供全局读取。
具体修改步骤
- 在
AppDelegate中添加属性存储Option键的实时状态
var isOptionKeyPressed: Bool = false
- 替换原有事件监听逻辑,同时添加全局+本地事件监听(补全应用前台时的事件监听能力),在回调中做状态比对
func applicationDidFinishLaunching(_ aNotification: Notification) { // 监听应用外的修饰键事件 NSEvent.addGlobalMonitorForEvents(matching: .flagsChanged, handler: keyEvent) // 监听应用内的修饰键事件,否则应用在前台时收不到回调 NSEvent.addLocalMonitorForEvents(matching: .flagsChanged) { [weak self] event in self?.keyEvent(event: event) return event } } func keyEvent(event: NSEvent) { // 直接使用回调传入的事件判断当前Option键状态,不要用NSApp.currentEvent避免取错事件 let currentOptionState = event.modifierFlags.contains(.option) // 状态无变化时直接返回,避免重复触发 guard isOptionKeyPressed != currentOptionState else { return } // 更新存储的实时状态 isOptionKeyPressed = currentOptionState if isOptionKeyPressed { print("Option键按下") // 可在此处触发展示高级菜单项的预逻辑 } else { print("Option键抬起") // 可在此处触发隐藏高级菜单项的逻辑 } }
注意事项
- 存储的
isOptionKeyPressed属性就是任意时刻可直接读取的Option键实时状态,在菜单打开前读取该值,即可决定是否插入额外的高级菜单项。 - 可以额外给状态栏菜单绑定
NSMenuDelegate,在menuWillOpen(_:)回调中再次校验NSApp.currentEvent?.modifierFlags.contains(.option)的状态,避免极端场景下事件监听漏判。 - 不要依赖
NSApp.currentEvent在全局事件回调中做判断,回调参数传入的event才是当前触发修饰键变化的对应事件,判断结果更可靠。
内容的提问来源于stack exchange,提问作者fsctl
相关产品推荐
相关产品推荐

