Code Repo: github.com/xuchi16/vis…
原文:xz3t11cmy1.feishu.cn/wiki/UaYSw4…
基本概念
visionOS中包含3种基本构建块,窗口(Windows)、空间容器(Volume)和空间(Space)。
为了理解清楚这3个概念,我们想构建一个应用,包含一个导航页面,其中有3个按钮,用来打开不同的构建块。
效果
步骤
构建3种View
Window
WindowView是一个普通的SwiftUI View,为了在其中添加3D对象,需要:
-
将USDZ类型的模型文件添加到项目中
-
使用RealityKit中的Model3D方法引用模型文件
struct WindowView: View {
var body: some View {
Model3D(named: "Sun")
}
}
在预览中,可以看到一个太阳的模型悬浮在窗口上方
在App中,新增WindowGroup,并给它一个id。
@main
struct SunApp: App {
var body: some Scene {
// ...
WindowGroup(id: "windowView") {
WindowView()
}
}
}
这样,我们就定义好了一个基本的Window。
Volume
Volume也是一个Window,在View的定义上是一样的,唯一的区别是在App中的WindowGroup,需要通过modifier将它的Window style声明为.volumetric
。
@main
struct SunApp: App {
var body: some Scene {
// ...
WindowGroup(id: "windowView") {
WindowView()
}
WindowGroup(id: "volumeView") {
VolumeView()
}.windowStyle(.volumetric)
// ...
}
}
Space
Space的View定义跟Window和Volume有所区别,需要通过RealityView加载场景,并在后续的闭包中加载对象。
struct ImmersiveView: View {
var body: some View {
RealityView { content in
// Add the initial RealityKit content
if let scene = try? await Entity(named: "Immersive", in: realityKitContentBundle) {
content.add(scene)
}
}
}
}
在App中,通过ImmersiveSpace
添加View。
@main
struct SunApp: App {
var body: some Scene {
// ...
ImmersiveSpace(id: "immersiveView") {
ImmersiveView()
}
}
}
在Reality Composer Pro中,我们可以编辑场景Immersive,导入模型并通过transform修改其位置。当打开Space时,就可以根据设定的场景加载了。
这样,我们就完成了3种场景的构建。
构建导航页
在导航页,我们需要3个按钮,每个按钮可以实现对应View的打开和关闭。想要通过一个按钮实现开关功能,可以使用SwiftUI中的Toggle。
Toggle功能
A control that toggles between on and off states.
在使用toggle时,需要定义一个bool变量绑定到isOn,用于决定其状态是开还是关。
@State private var vibrateOnRing = true
var body: some View {
Toggle(
"Vibrate on Ring",
systemImage: "dot.radiowaves.left.and.right",
isOn: $vibrateOnRing
)
}
通过按钮打开/关闭Space,首先需要定义Toggle:
- 新建一个Toggle,通过
toggleStyle
定义为按钮类型 - 声明一个bool类型变量
isImmersiveSpaceShown
,绑定到Toggle控件的isOn
上,存储toggle的当前状态 - 通过
onChange
注册用户点击按钮会触发的动作,即打开/关闭immersive space
这样就完成了按钮样式和行为的定义
struct ContentView: View {
@State private var isImmersiveSpaceShown = false
var body: some View {
// ...
Toggle("Space", isOn: $isImmersiveSpaceShown)
.toggleStyle(.button)
.onChange(of: isImmersiveSpaceShown) {
// open/dismiss space
}
}
}
为了在用户点击Toggle时打开/关闭Space,需要在View中通过@Environment
获取环境中openImmersiveSpace
,它的类型是OpenImmersiveSpaceAction
。
OpenImmersiveSpaceAction
定义了callAsFunction(id:)
方法,签名如下。因此可以直接调用该方法,通过传递特定的id打开指定的沉浸式空间。该方法为异步方法,根据调用时沉浸空间打开的状态不同,返回的结果也是不同的,这里我们暂时忽略调用的返回结果。
@discardableResult
@MainActor public func callAsFunction(id: String) async -> OpenImmersiveSpaceAction.Result
根据上述内容,可以通过如下代码实现对沉浸空间的打开/关闭。
struct ContentView: View {
@State private var isImmersiveSpaceShown = false
@Environment(.openImmersiveSpace) private var openImmersiveSpace
@Environment(.dismissImmersiveSpace) private var dismissImmersiveSpace
var body: some View {
// ...
Toggle("Space", isOn: $isImmersiveSpaceShown)
.toggleStyle(.button)
.onChange(of: isImmersiveSpaceShown) { _, show in
Task {
if show {
await openImmersiveSpace(id: "immersiveView")
} else {
await dismissImmersiveSpace()
}
}
}
}
}
Window的打开/关闭的实现也是类似的,但openWindow
和dismissWindow
是同步的,所以略有差异。
struct ContentView: View {
@State private var isVolumeWindowShown = false
@Environment(.openWindow) private var openWindow
@Environment(.dismissWindow) private var dismissWindow
var body: some View {
// ...
Toggle("Volume", isOn: $isVolumeWindowShown)
.toggleStyle(.button)
.onChange(of: isVolumeWindowShown) { _, show in
if show {
openWindow(id: "volumeView")
} else {
dismissWindow(id: "volumeView")
}
}
}
}
结果
这样,我们就可以通过页面控制3种类型构建块的加载了
- 中间:导航页
- 顶部:Window
- 左边:Volume
- 右侧:Space(透视情况下将物体放置在右侧)