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

原示例代码
@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
相关产品推荐
相关产品推荐

