VisionPro开发 - 窗口,空间容器和空间

首页:漫游Apple Vision Pro

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方法引用模型文件

swift 复制代码
struct WindowView: View {
    var body: some View {
        Model3D(named: "Sun")
    }
}

在预览中,可以看到一个太阳的模型悬浮在窗口上方

在App中,新增WindowGroup,并给它一个id。

swift 复制代码
 @main
struct SunApp: App {
    var body: some Scene {
        // ...
  
        WindowGroup(id: "windowView") {
            WindowView()
        }
    }
}

这样,我们就定义好了一个基本的Window。

Volume

Volume也是一个Window,在View的定义上是一样的,唯一的区别是在App中的WindowGroup,需要通过modifier将它的Window style声明为.volumetric

swift 复制代码
 @main
struct SunApp: App {
    var body: some Scene {
        // ...
        
        WindowGroup(id: "windowView") {
            WindowView()
        }
        
        WindowGroup(id: "volumeView") {
            VolumeView()
        }.windowStyle(.volumetric)
        
        // ...
    }
}

Space

Space的View定义跟Window和Volume有所区别,需要通过RealityView加载场景,并在后续的闭包中加载对象。

swift 复制代码
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。

swift 复制代码
 @main
struct SunApp: App {
    var body: some Scene {
        // ...
        
        ImmersiveSpace(id: "immersiveView") {
            ImmersiveView()
        }
    }
}

在Reality Composer Pro中,我们可以编辑场景Immersive,导入模型并通过transform修改其位置。当打开Space时,就可以根据设定的场景加载了。

这样,我们就完成了3种场景的构建。

构建导航页

在导航页,我们需要3个按钮,每个按钮可以实现对应View的打开和关闭。想要通过一个按钮实现开关功能,可以使用SwiftUI中的Toggle。

Toggle功能

Toggle Doc

A control that toggles between on and off states.

在使用toggle时,需要定义一个bool变量绑定到isOn,用于决定其状态是开还是关。

swift 复制代码
@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

这样就完成了按钮样式和行为的定义

swift 复制代码
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打开指定的沉浸式空间。该方法为异步方法,根据调用时沉浸空间打开的状态不同,返回的结果也是不同的,这里我们暂时忽略调用的返回结果。

swift 复制代码
    @discardableResult
    @MainActor public func callAsFunction(id: String) async -> OpenImmersiveSpaceAction.Result

根据上述内容,可以通过如下代码实现对沉浸空间的打开/关闭。

swift 复制代码
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的打开/关闭的实现也是类似的,但openWindowdismissWindow是同步的,所以略有差异。

swift 复制代码
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(透视情况下将物体放置在右侧)
相关推荐
HarderCoder5 个月前
Apple Vision Pro 学习资料
visionos
苹果API搬运工5 个月前
试玩 RealityComposerPro 中的 Shader Graph:用圆环制作一个 Meta Logo
visionos·增强现实
苹果API搬运工5 个月前
我开源了个手势匹配框架,让你在模拟器调试 visionOS 手部追踪功能!
visionos·增强现实
苹果API搬运工5 个月前
只需三板斧!带你入门 visionOS 空间计算的数学与几何基础
visionos·增强现实
-九月新辰-6 个月前
Unity VisionOS开发流程
unity·游戏引擎·visionos
XR基地6 个月前
XR 世界导览021 | AVP App Store上线网页版、如何在AVP上快速匹配手势、AVP开发岗位
visionos
XR基地6 个月前
XR 世界导览#020-visionOS 1.1/Xcode 17.3/PICO 5.9.0 更新了!
unity3d·visionos
xChester6 个月前
visionOS 应用图标设计
前端·ios·visionos
XR基地6 个月前
XR 世界导览#019-Let's visionOS 大会/在 AVP 上玩手机或查看Spline的3D模型/用代码写ShaderGraph
unity3d·visionos
xChester7 个月前
VisionPro开发 - 轻松实现天空盒并添加光照
前端·ios·visionos