背景
在体验HelloWorld时,很好奇每个功能是怎么实现的,但是这个demo复用了很多功能、数据模型,刚开始理解起来就比较困难。所以我就先从功能点来看,将复用的功能、数据模型都剔除掉,保证单一功能能解藕单独运行。
环境
Xcode:15.1 beta
VisionOS:1.0
梳理功能
沉浸式与窗口之间的切换

1. 打开沉浸式空间
swift
import SwiftUI
@main
struct MyWorldApp: App {
@State private var solarImmersionStyle: ImmersionStyle = .full
@State private var model = ViewModel()
var body: some Scene {
// 1.不带边框的window
WindowGroup{
SolarDisplayView()
.environment(model)
}
.windowStyle(.plain)
// 2.沉浸式空间
ImmersiveSpace(id: Module.solar.name) {
SolarView()
.environment(model)
}
.immersionStyle(selection: $solarImmersionStyle, in: .full)
}
// 3. 注册的Component、System
init() {
RotationComponent.registerComponent()
RotationSystem.registerSystem()
TraceComponent.registerComponent()
TraceSystem.registerSystem()
}
}
1.1 定义沉浸式空间
在打开沉浸式空间之前,我们需要定义一个沉浸式空间,在App
里面作为Scene
返回。
Swift
ImmersiveSpace(id: Module.solar.name) {
SolarView()
.environment(model)
}
1.2 打开/关闭空间
swift
// 打开
@Environment(\.openImmersiveSpace) private var openImmersiveSpace
Task {
await openImmersiveSpace(id: Module.solar.name)
}
swift
// 关闭
@Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
Task {
await dismissImmersiveSpace()
}
注意: 我们在打开空间openImmersiveSpace
后,之前的window并不会消失,还会一直保留。也就是说,即使你进入了沉浸式空间后,之前的window也会进入沉浸式空间,同一时间会存在一个window、一个space。
为了更多集中在Space上面,我们常常会对之前的window做一些隐藏、关闭等操作。
2.隐藏window
这里的隐藏,就是改变原有Window的透明度,把无关的信息隐藏掉,看起来window就"消失"了,其实它并没有关闭。
swift
WindowGroup{
SolarDisplayView()
.environment(model)
}
.windowStyle(.plain)
在隐藏之前,一定要设置.windowStyle(.plain)
,不然会一直显示毛玻璃窗口。
swift
import SwiftUI
// 进入沉浸式空间的入口
struct SolarDisplayView: View {
@Environment(ViewModel.self) private var model
var body: some View {
ZStack {
// 退出沉浸式空间的开关
SolarSystemToggle()
.opacity(model.isShowingSolar ? 1 : 0)
VStack{
Text("进入沉浸式空间")
.font(.system(size: 50, weight: .bold))
.padding(.bottom, 15)
// 进入沉浸式空间的开关
SolarSystemToggle()
}
.opacity(model.isShowingSolar ? 0 : 1)
}
}
}
#Preview {
SolarDisplayView()
.environment(ViewModel())
}
这里逻辑很简单,就是进入沉浸式空间前,显示进入开关。
进入沉浸式空间后,显示退出开关,其实是同一个window。
这里会有几个疑问:
1. 为什么不直接关闭Window
swift
@Environment(\.dismissWindow) private var dismissWindow
Task {
await dismissWindow()
}
我们确实可以先关闭window,然后再打开window,但实际使用过程中会出现闪烁的情况,所以才使用隐藏、显示。
2. 退出开关还可以怎么做?
可以直接放在Space空间,但是需要自己调整位置
可以用RealityView.attachment
,也需要调整位置
3. 为什么Space、window可以同时存在
我最开始会有这个疑问,为什么能同时存在,做成导航栈一样岂不是更好,一个打开,另一个关闭。直到我看到Destination Video,我才知道它们一起存在的意义。里面展示一个视频播放的demo,最开始是一个视频列表窗口,有导航栈点击进入详情。详情展示了播放窗口,又展示了一个全景的沉浸式空间。在接触这个demo之前,我一直以为播放器也在沉浸式空间里面,但是始终没有达到想要的效果,最终才发现window、space同时存在才能实现。
3.代码
入口是SolarDisplayView,MyWorldApp
启动入口也是它,直接Run
就可以。