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

SwiftUI WindowGroup在iPad上重复打开同ID窗口问题

iPad上WindowGroup重复打开同ID窗口的修复方案

问题说明

我需要实现一个数据驱动的WindowGroup,让应用可以启动额外的辅助窗口。但在iPad设备上,WindowGroup无法识别对应ID的窗口已处于打开状态,每次点击都会打开一个相同ID的新窗口;而Mac设备上表现正常,会直接将对应ID的窗口前置显示。

iPad上重复打开窗口的截图

原示例代码

@main
struct MultiWindowSimpleApp: App {
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        // 辅助窗口
        WindowGroup(id: "AnItem", for: Item.ID.self) { $anID in
            if let anID, let item = Items.main.item(withID: anID ) {
                Text("\(item.id)")
            } else {
                Text("未找到项目!")
            }
        }
    }
}

// =======================================================
struct ContentView: View {
    let content = Items()
    var body: some View {
        VStack {
            ForEach(content.items) { anItem in
                ItemView(item: anItem )
            }
        }
        .padding()
    }
}

// 辅助内容
// =======================================================
/// 项目基础结构体
struct Item: Identifiable, Hashable, Codable {
    var id: UUID = UUID()
}

// =======================================================
/// 创建并存储多个项目的结构体
struct Items {
    static var main: Items!
    var items: [Item]
    
    init() {
        var items: [Item] = []
        for _ in 0..<5 {
            items.append(Item())
        }
        self.items = items
        Self.main = self
    }
    
    func item(withID id: UUID) -> Item? {
        return items.first(where: { $0.id == id })
    }
}

// =======================================================
/// 显示项目ID,点击时打开对应窗口
struct ItemView: View {
    @Environment(\.openWindow) private var openWindow
    @Environment(\.supportsMultipleWindows) private var supportsMultipleWindows
    let item: Item
    var body: some View {
        Text("\(item.id)")
            .font(.system(size: 13))
            .frame(maxWidth: .infinity)
            .padding()
            .background(.yellow)
            .onTapGesture {
                if supportsMultipleWindows {
                    openWindow(value: item.id)
                }
            }
    }
}

修复方案

问题根源在于iPad上的WindowGroup默认不会自动复用已打开的同值窗口,需要手动跟踪窗口状态并控制打开逻辑。修改步骤如下:

  • 在App结构体中添加状态变量跟踪已打开的项目ID
  • 实现自定义窗口打开方法,优先激活已存在的窗口
  • 监听窗口的打开/关闭事件,更新跟踪集合

修改后的完整代码

@main
struct MultiWindowSimpleApp: App {
    // 跟踪已打开的项目ID集合
    @State private var openItemIDs: Set<UUID> = []
    
    var body: some Scene {
        WindowGroup {
            ContentView()
        }
        // 辅助窗口,添加onChange监听窗口状态变化
        WindowGroup(id: "AnItem", for: Item.ID.self) { $anID in
            if let anID, let item = Items.main.item(withID: anID) {
                Text("\(item.id)")
                    .onAppear {
                        // 窗口出现时添加ID到跟踪集合
                        openItemIDs.insert(anID)
                    }
                    .onDisappear {
                        // 窗口消失时从跟踪集合移除ID
                        openItemIDs.remove(anID)
                    }
            } else {
                Text("未找到项目!")
            }
        }
        // 给ContentView注入自定义的窗口打开方法
        .environment(\.openOrActivateWindow, openOrActivateWindow)
    }
    
    // 自定义方法:激活已存在的窗口,否则创建新窗口
    private func openOrActivateWindow(id: UUID) {
        if openItemIDs.contains(id) {
            // 激活已存在的窗口
            openWindow(id: "AnItem", value: id)
        } else {
            // 创建新窗口
            openWindow(value: id)
        }
    }
}

// 自定义环境键,用于传递窗口打开方法
private struct OpenOrActivateWindowKey: EnvironmentKey {
    static let defaultValue: (UUID) -> Void = { _ in }
}

extension EnvironmentValues {
    var openOrActivateWindow: (UUID) -> Void {
        get { self[OpenOrActivateWindowKey.self] }
        set { self[OpenOrActivateWindowKey.self] = newValue }
    }
}

// =======================================================
struct ContentView: View {
    let content = Items()
    var body: some View {
        VStack {
            ForEach(content.items) { anItem in
                ItemView(item: anItem )
            }
        }
        .padding()
    }
}

// 辅助内容
// =======================================================
/// 项目基础结构体
struct Item: Identifiable, Hashable, Codable {
    var id: UUID = UUID()
}

// =======================================================
/// 创建并存储多个项目的结构体
struct Items {
    static var main: Items!
    var items: [Item]
    
    init() {
        var items: [Item] = []
        for _ in 0..<5 {
            items.append(Item())
        }
        self.items = items
        Self.main = self
    }
    
    func item(withID id: UUID) -> Item? {
        return items.first(where: { $0.id == id })
    }
}

// =======================================================
/// 显示项目ID,点击时打开/激活对应窗口
struct ItemView: View {
    @Environment(\.openOrActivateWindow) private var openOrActivateWindow
    @Environment(\.supportsMultipleWindows) private var supportsMultipleWindows
    let item: Item
    var body: some View {
        Text("\(item.id)")
            .font(.system(size: 13))
            .frame(maxWidth: .infinity)
            .padding()
            .background(.yellow)
            .onTapGesture {
                if supportsMultipleWindows {
                    openOrActivateWindow(item.id)
                }
            }
    }
}

说明

  • 通过openItemIDs集合跟踪所有已打开的窗口对应的项目ID
  • 自定义openOrActivateWindow方法,判断ID是否已存在:存在则调用openWindow(id:value:)激活已有窗口,不存在则创建新窗口
  • 使用自定义环境键将打开方法传递给子视图,避免直接依赖App结构体的状态

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 01:04:59