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

visionOS中SwiftUI如何将多个WindowGroup置于ContentView上方且主视图可见

实现visionOS多窗口层级控制与单窗口弹出逻辑

核心解决思路

  1. 给主窗口和弹窗窗口设置明确的层级,确保主窗口始终在底层
  2. 通过ViewModel集中管理弹窗状态,保证同一时间仅弹出一个窗口
  3. 用程序内状态控制窗口的打开/关闭,替代外部事件触发的不可控方式

修改后的完整代码

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 11:16:03