You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

macOS SwiftUI:在Window菜单添加带快捷键的可切换菜单项

在macOS SwiftUI中实现带勾选状态的Window菜单开关项

需求与问题

需要在macOS SwiftUI应用的Window菜单中添加一个类似Xcode「Window」→「Developers Documentation」的菜单项,要求:

  • 菜单项带有快捷键
  • 窗口打开时显示勾选标记
  • 点击菜单项可切换窗口的开闭状态

尝试过Toggle、Button组件,遇到的问题:

  • Button仅能单次打开窗口,关闭后无法再次触发打开
  • Toggle仅能完成一次开闭操作,后续失效
  • 使用StateObject后代码可运行,但日志出现onChange(of: Bool) action tried to update multiple times per frame错误,快速按快捷键会生成重复窗口

解决方案

核心思路是让菜单项的勾选状态与窗口的实际生命周期同步,利用SwiftUI环境变量和窗口事件监听控制状态,避免重复触发和重复窗口。

完整代码示例

import SwiftUI

// 全局状态管理,同步窗口打开状态
class MenuState: ObservableObject {
    @Published var isProductWindowOpen = false
    
    // 防止重复触发的标记
    private var isProcessing = false
    
    func toggleProductWindow() {
        guard !isProcessing else { return }
        isProcessing = true
        
        if isProductWindowOpen {
            // 关闭指定标题的窗口
            NSApplication.shared.windows.forEach { window in
                if window.title == "Products" {
                    window.close()
                }
            }
        } else {
            // 触发系统打开窗口的动作
            NSApplication.shared.sendAction(Selector(("showWindow:")), to: nil, from: nil)
        }
        
        // 延迟重置标记,避免同一帧内多次触发
        DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
            self.isProcessing = false
        }
    }
    
    // 从窗口生命周期事件更新状态
    func updateWindowState(isOpen: Bool) {
        if isProductWindowOpen != isOpen {
            isProductWindowOpen = isOpen
        }
    }
}

@main
struct MenuItemInsertWindowApp: App {
    @StateObject private var menuState = MenuState()
    
    // 监听应用窗口事件的代理
    @NSApplicationDelegateAdaptor(AppDelegate.self) private var appDelegate
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        
        WindowGroup("Products") {
            ProductsView()
                .onAppear {
                    menuState.updateWindowState(isOpen: true)
                }
                .onDisappear {
                    menuState.updateWindowState(isOpen: false)
                }
        }
        .commands {
            // 替换默认的Window列表组,插入自定义菜单项
            CommandGroup(replacing: .windowList) {
                Toggle("Products", isOn: $menuState.isProductWindowOpen)
                    .onChange(of: menuState.isProductWindowOpen) { _ in
                        menuState.toggleProductWindow()
                    }
                    .keyboardShortcut("P", modifiers: [.command, .shift]) // 设置快捷键
            }
        }
    }
    
    // 应用代理,处理手动关闭窗口的情况
    class AppDelegate: NSObject, NSApplicationDelegate {
        func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool {
            return true
        }
        
        func applicationWillClose(_ notification: Notification) {
            guard let window = notification.object as? NSWindow, window.title == "Products" else { return }
            menuState.updateWindowState(isOpen: false)
        }
    }
}

struct ContentView: View {
    var body: some View {
        Text("ContentView")
            .frame(minWidth: 600, maxWidth: 600, minHeight: 400, maxHeight: 400)
    }
}

struct ProductsView: View {
    var body: some View {
        Text("Products Window")
            .frame(minWidth: 500, maxWidth: 500, minHeight: 300, maxHeight: 300)
    }
}

关键部分说明

  • MenuState状态管理:
    专门维护窗口的打开状态,加入isProcessing开关防止短时间内重复操作(比如快速按快捷键),彻底解决重复窗口和日志报错问题。updateWindowState方法用来同步窗口实际的显示/隐藏状态,保证菜单项勾选标记和窗口状态一致。

  • 窗口生命周期同步:
    在ProductsView的onAppear和onDisappear中更新状态,用户手动关闭窗口时,通过AppDelegate的applicationWillClose事件同步状态,不会出现窗口已关闭但菜单项仍勾选的情况。

  • 菜单实现:
    用Toggle组件实现自带勾选效果的菜单项,绑定状态后,点击或按快捷键都会触发toggleProductWindow控制窗口开闭,同时状态自动同步更新。


内容的提问来源于stack exchange,提问作者iPadawan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 08:15:43