visionOS中SwiftUI如何将多个WindowGroup置于ContentView上方且主视图可见
实现visionOS多窗口层级控制与单窗口弹出逻辑
核心解决思路
- 给主窗口和弹窗窗口设置明确的层级,确保主窗口始终在底层
- 通过ViewModel集中管理弹窗状态,保证同一时间仅弹出一个窗口
- 用程序内状态控制窗口的打开/关闭,替代外部事件触发的不可控方式
修改后的完整代码
1. 窗口管理ViewModel
import SwiftUI class OptionsViewModel: ObservableObject { // 记录当前打开的弹窗ID,nil表示无弹窗 @Published var currentPopupWindow: String? func openWindow(_ id: String) { // 先关闭已打开的弹窗 if let current = currentPopupWindow { UIApplication.shared.sendAction(#selector(UIWindowScene.closeWindow), to: nil, from: current, for: nil) } // 打开新窗口 currentPopupWindow = id UIApplication.shared.sendAction(#selector(UIWindowScene.openWindow), to: nil, from: id, for: nil) } func closeCurrentWindow() { if let current = currentPopupWindow { UIApplication.shared.sendAction(#selector(UIWindowScene.closeWindow), to: nil, from: current, for: nil) currentPopupWindow = nil } } } // 扩展UIWindowScene实现窗口精准控制 extension UIWindowScene { @objc func openWindow(_ sender: Any?) { guard let windowId = sender as? String else { return } self.openWindow(id: windowId) } @objc func closeWindow(_ sender: Any?) { guard let windowId = sender as? String else { return } if let window = self.windows.first(where: { $0.scene?.session.configuration.name == windowId }) { window.close() } } }
2. App主结构
@main struct Demo17App: App { // 用StateObject确保ViewModel全局唯一 @StateObject private var viewModel = OptionsViewModel() var body: some Scene { // 主窗口 - 始终在底层显示 WindowGroup { ContentView(viewModel: viewModel) .environmentObject(viewModel) } .windowStyle(.plain) .windowLevel(.normal) // 普通层级,确保为底层 .defaultSize(width: 800, height: 600) // 弹窗窗口1 WindowGroup(id: "firstWindow") { Text("Second Window Content [Information]") .frame(width: 300, height: 200) .background(Color.blue) } .windowStyle(.plain) .windowLevel(.statusBar) // 层级高于主窗口 .defaultSize(width: 300, height: 200) // 弹窗窗口2 WindowGroup(id: "secondWindow") { VideoView() } .windowLevel(.statusBar) .defaultSize(width: 400, height: 300) // 弹窗窗口3 WindowGroup(id: "thirdWindow") { ImmersiveView() } .windowLevel(.statusBar) .defaultSize(width: 500, height: 400) } }
3. 主ContentView示例(带触发按钮)
struct ContentView: View { @EnvironmentObject var viewModel: OptionsViewModel var body: some View { VStack(spacing: 20) { Text("主内容区域 - 始终可见") .font(.title) Button("打开信息窗口") { viewModel.openWindow("firstWindow") } Button("打开视频窗口") { viewModel.openWindow("secondWindow") } Button("打开沉浸式窗口") { viewModel.openWindow("thirdWindow") } Button("关闭当前弹窗") { viewModel.closeCurrentWindow() } } .frame(maxWidth: .infinity, maxHeight: .infinity) .background(Color.gray.opacity(0.2)) } }
关键细节说明
- 层级控制:通过
windowLevel区分层级,.normal是默认底层,.statusBar优先级更高,确保弹窗始终在主窗口上方 - 单窗口限制:ViewModel的
openWindow方法会先关闭已打开的弹窗,再打开新窗口,从逻辑上保证同一时间仅显示一个弹窗 - 窗口移动:所有
WindowGroup窗口支持visionOS原生的拖拽移动操作,无需额外代码 - 主窗口常驻:主窗口没有关闭逻辑,始终保持显示,弹窗仅覆盖部分区域,不会完全遮挡主内容
内容的提问来源于stack exchange,提问作者kartbouni
相关产品推荐
相关产品推荐

