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

macOS SwiftUI多窗口专属命令实现:如何让命令仅对应窗口生效?

SwiftUI 窗口专属命令实现问题

我尝试为应用的每个窗口添加专属命令(代码示例如下),但即使在两个Window上分别使用.commands修饰符,所有命令始终处于可用状态。请问有没有原生SwiftUI方式,让第一/第二组命令仅分别在第一/第二窗口显示?至少能实现启用/禁用状态切换?

import SwiftUI

@main
struct CommandsTestProjectApp: App {
    @Environment(\.openWindow) private var openWindow

    var body: some Scene {
        // FIRST WINDOW
        Window("First Window", id: "FirstWindow") {
            Text("First Window")
            Button("Open Second Window") {
                openWindow.callAsFunction(id: "SecondWindow")
            }
        }
        .commands {
            FirstCommands()
        }

        // SECOND WINDOW
        Window("Second Window", id: "SecondWindow") {
            Text("Second Window")
        }
        .commands {
            SecondCommands()
        }
    }
}

struct FirstCommands: Commands {
    var body: some Commands {
        CommandGroup(after: .newItem) {
            Button("First button") {

            }
        }
    }
}

struct SecondCommands: Commands {
    var body: some Commands {
        CommandGroup(after: .newItem) {
            Button("Second button") {

            }
        }
    }
}

菜单栏同时显示两个按钮的截图


解决方案

核心思路是追踪当前激活的窗口ID,以此为依据控制命令的显示或启用状态。

1. 实现命令仅对应窗口显示

通过创建共享状态对象管理当前激活窗口,在命令组中根据状态判断是否渲染按钮:

import SwiftUI

// 管理当前激活窗口的状态对象
class ActiveWindowManager: ObservableObject {
    @Published var activeWindowID: String?
}

@main
struct CommandsTestProjectApp: App {
    @Environment(\.openWindow) private var openWindow
    // 创建全局共享的状态对象
    @StateObject private var windowManager = ActiveWindowManager()

    var body: some Scene {
        Window("First Window", id: "FirstWindow") {
            Text("First Window")
            Button("Open Second Window") {
                openWindow(id: "SecondWindow")
            }
        }
        // 监听窗口激活状态,更新当前窗口ID
        .onChange(of: ScenePhase.active) { newPhase in
            if newPhase == .active {
                windowManager.activeWindowID = "FirstWindow"
            }
        }
        .commands {
            FirstCommands(windowManager: windowManager)
        }

        Window("Second Window", id: "SecondWindow") {
            Text("Second Window")
        }
        .onChange(of: ScenePhase.active) { newPhase in
            if newPhase == .active {
                windowManager.activeWindowID = "SecondWindow"
            }
        }
        .commands {
            SecondCommands(windowManager: windowManager)
        }
    }
}

struct FirstCommands: Commands {
    @ObservedObject var windowManager: ActiveWindowManager

    var body: some Commands {
        CommandGroup(after: .newItem) {
            // 仅当前窗口激活时显示按钮
            if windowManager.activeWindowID == "FirstWindow" {
                Button("First button") {
                    // 执行命令逻辑
                }
            }
        }
    }
}

struct SecondCommands: Commands {
    @ObservedObject var windowManager: ActiveWindowManager

    var body: some Commands {
        CommandGroup(after: .newItem) {
            if windowManager.activeWindowID == "SecondWindow" {
                Button("Second button") {
                    // 执行命令逻辑
                }
            }
        }
    }
}

2. 实现命令启用/禁用切换

如果不想隐藏按钮,仅在非对应窗口时禁用,用disabled修饰符替代条件判断即可:

// 以FirstCommands为例,SecondCommands逻辑一致
struct FirstCommands: Commands {
    @ObservedObject var windowManager: ActiveWindowManager

    var body: some Commands {
        CommandGroup(after: .newItem) {
            Button("First button") {
                // 执行命令逻辑
            }
            // 当前窗口非目标窗口时禁用按钮
            .disabled(windowManager.activeWindowID != "FirstWindow")
        }
    }
}

注意事项

  • 窗口激活状态依赖ScenePhase监听,窗口从后台切回前台时会自动更新状态。
  • 若需支持更多窗口,只需扩展activeWindowID的判断逻辑即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 15:43:35