如何在SwiftUI的MenuBarExtra中添加右键菜单选项?
SwiftUI实现状态栏图标右键菜单解决方案
在SwiftUI的MenuBarExtra组件中,默认仅支持左键点击展开菜单。要实现右键点击状态栏图标弹出设置菜单的功能,需要通过桥接AppKit的NSView和NSMenu来实现,以下是完整的解决方案:
实现步骤
- 自定义
NSViewRepresentable视图,包装状态栏图标并监听左右键点击事件 - 在
MenuBarExtra中使用自定义视图作为标签,绑定左键点击的菜单展开逻辑 - 为右键点击创建自定义
NSMenu,并与AppSettings状态绑定
完整代码
import SwiftUI import AppKit class AppSettings: ObservableObject { @Published var sendText = true { didSet { if !sendText { playSound = true } } } @Published var playSound = true { didSet { if !playSound { sendText = true } } } } // 自定义状态栏视图,处理左右键点击事件 struct StatusBarView: NSViewRepresentable { @ObservedObject var settings: AppSettings var leftClickAction: () -> Void func makeNSView(context: Context) -> NSView { let view = NSView() view.wantsLayer = true view.frame = NSRect(x: 0, y: 0, width: 22, height: 22) // 监听右键点击 let rightClickGesture = NSClickGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.showRightMenu)) rightClickGesture.buttonMask = 0x2 // 右键掩码 view.addGestureRecognizer(rightClickGesture) // 监听左键点击,触发原菜单展开 let leftClickGesture = NSClickGestureRecognizer(target: context.coordinator, action: #selector(Coordinator.handleLeftClick)) leftClickGesture.buttonMask = 0x1 // 左键掩码 view.addGestureRecognizer(leftClickGesture) // 添加状态栏图标(可替换为自定义图标) let imageView = NSImageView(image: NSImage(systemSymbolName: "app", accessibilityDescription: "App")!) imageView.frame = view.bounds view.addSubview(imageView) return view } func updateNSView(_ nsView: NSView, context: Context) {} func makeCoordinator() -> Coordinator { Coordinator(self) } class Coordinator: NSObject { let parent: StatusBarView init(_ parent: StatusBarView) { self.parent = parent } @objc func handleLeftClick() { parent.leftClickAction() } @objc func showRightMenu() { let menu = NSMenu() // 发送文本开关选项 let sendTextItem = NSMenuItem(title: "发送文本", action: #selector(toggleSendText), keyEquivalent: "") sendTextItem.state = parent.settings.sendText ? .on : .off sendTextItem.target = self menu.addItem(sendTextItem) // 播放声音开关选项 let playSoundItem = NSMenuItem(title: "播放声音", action: #selector(togglePlaySound), keyEquivalent: "") playSoundItem.state = parent.settings.playSound ? .on : .off playSoundItem.target = self menu.addItem(playSoundItem) // 分隔线与退出选项 menu.addItem(NSMenuItem.separator()) let quitItem = NSMenuItem(title: "退出", action: #selector(NSApplication.terminate(_:)), keyEquivalent: "q") menu.addItem(quitItem) // 在当前鼠标位置弹出菜单 if let event = NSApp.currentEvent { NSApp.popUpContextMenu(menu, with: event, for: parent.makeNSView(context: Context())) } } @objc func toggleSendText() { parent.settings.sendText.toggle() } @objc func togglePlaySound() { parent.settings.playSound.toggle() } } } @main struct SudoApp: App { @StateObject var settings = AppSettings() @State private var isMenuOpen = false var body: some Scene { MenuBarExtra(isInserted: $isMenuOpen) { // 左键点击显示的菜单内容(保留原有逻辑) Button(action:{}) { HStack { Image(systemName: "alarm") Text("Toggle") Spacer() } } } label: { // 使用自定义视图替代默认标题,处理点击事件 StatusBarView(settings: settings) { isMenuOpen.toggle() } } } }
关键说明
- 通过
NSViewRepresentable实现SwiftUI与AppKit的桥接,直接处理系统级鼠标事件 - 右键菜单使用AppKit的
NSMenu创建,支持与AppSettings状态绑定,实现开关实时同步 - 左键点击保留原有
MenuBarExtra的菜单展开逻辑,保证功能兼容
内容的提问来源于stack exchange,提问作者alex
相关产品推荐
相关产品推荐

