WWDC Quick Look 💓 By SwiftGGTeam
Develop your first immersive app

Develop your first immersive app

观看原视频

Highlight

visionOS App 开发沿用熟悉的 Xcode + SwiftUI 工作流,通过 Window、Volume、Immersive Space 三种场景类型和 RealityView 桥接 3D 内容,开发者可以在几小时内从零搭建出可交互的沉浸式体验。

核心内容

做 VR/AR 开发,过去最痛苦的是工具链。Unity 或 Unreal 虽然强大,但学习曲线陡峭,和 iOS 开发完全是两套体系。

visionOS 的做法不同:它把空间计算能力直接集成到开发者已经熟悉的工具里。Xcode 还是 Xcode,SwiftUI 还是 SwiftUI,只是多了几个新场景类型和一个 RealityView。

三种场景类型

创建 visionOS 项目时,Xcode 模板提供三种初始场景选择:(01:56

Window:主要用于 2D 内容,平面尺寸可调,深度固定。和其他 App 并排显示在 Shared Space 中。适合传统 App 的迁移。

Volume:用于 3D 内容,三个维度的尺寸由 App 控制,用户不能调整。也运行在 Shared Space,但可以展示立体模型、数据可视化等内容。

Immersive Space:无边界的沉浸式场景,激活时 App 从 Shared Space 进入 Full Space,其他 App 被隐藏。可以请求 ARKit 功能如手部追踪。

Immersive Space 有三种沉浸风格:(03:50

  • Mixed:虚拟内容叠加在透视环境上,用户仍能看到真实世界
  • Progressive:打开一个约 180 度的视口,用户可用 Digital Crown 调节视口大小
  • Full:完全隐藏透视,用户被虚拟环境包围

Apple 建议 App 始终从 Window 开始,提供明确的按钮让用户主动进入沉浸模式。不要在用户不知情的情况下强制切换。(05:04

RealityView:SwiftUI 和 3D 的桥梁

RealityView 是 visionOS 上连接 SwiftUI 和 RealityKit 的核心组件。它接收两个闭包:(07:28

  • make:异步初始化 RealityKit 内容,可以加载 .usda 场景文件,也可以程序化创建
  • update:在 SwiftUI 状态变化时调用,用于更新 3D 内容

update 闭包只在 SwiftUI 状态变化时触发,不是每帧调用的渲染循环。这个设计很重要,避免开发者误把它当游戏循环用。

从 Volume 到 Immersive Space

Session 演示了一个完整的开发流程:先用模板创建一个 Volume 项目,在 ContentView 里用 RealityView 加载一个 3D 球体模型,加上 Toggle 按钮和点击手势控制缩放。(06:24

然后在 Reality Composer Pro 中创建一个新场景,导入 USDZ 云朵模型,调整位置和大小。回到 Xcode,在 App.swift 中添加 ImmersiveSpace 场景声明,在 ContentView 中添加按钮用 openImmersiveSpace 打开沉浸空间。(20:31

Immersive Space 的坐标系以用户双脚位置为原点,X 轴向右,Y 轴向上,Z 轴负方向朝前。内容放置后位置固定,用户通过移动身体来探索空间。(16:04

实体交互

让 3D 物体响应点击,需要两个条件:(25:48

  1. 实体必须有 CollisionComponent,定义碰撞体积
  2. 实体必须有 InputTargetComponent,标记为可交互目标

这两个组件可以在 Reality Composer Pro 中添加,也可以用代码添加。然后给 RealityView 附加 .targetedToAnyEntity() 手势修饰符,就能精确识别用户点击了哪个实体。

Session 演示了点击云朵后让它飘动的效果,用 entity.move(to:relativeTo:duration:timingFunction:) 实现平滑动画。(28:56

详细内容

Xcode 项目模板配置

创建 visionOS 项目时,Initial Scene 选择 Volume,Immersive Space 先选 None。项目生成后会包含:

  • MyFirstImmersiveApp.swift:App 入口,声明 WindowGroup
  • ContentView.swift:主视图,包含 RealityView 和 UI 控件
  • RealityKitContent Swift Package:存放 3D 内容
// MyFirstImmersiveApp.swift
@main
struct MyFirstImmersiveApp: App {
    var body: some Scene {
        WindowGroup {
            ContentView()
        }.windowStyle(.volumetric)

        ImmersiveSpace(id: "ImmersiveSpace") {
            ImmersiveView()
        }
    }
}

关键点:

  • .windowStyle(.volumetric) 把 WindowGroup 变成 Volume,支持 3D 内容
  • ImmersiveSpace 需要唯一 ID,后续通过 ID 打开
  • 第一个 scene 是 App 启动时默认展示的

RealityView 的完整用法

RealityView { content in
    // make 闭包:异步加载初始内容
    if let scene = try? await Entity(named: "Scene", in: realityKitContentBundle) {
        content.add(scene)
    }
} update: { content in
    // update 闭包:响应 SwiftUI 状态变化
    if let scene = content.entities.first {
        let uniformScale: Float = enlarge ? 1.4 : 1.0
        scene.transform.scale = [uniformScale, uniformScale, uniformScale]
    }
}
.gesture(TapGesture().targetedToAnyEntity().onEnded { _ in
    enlarge.toggle()
})

关键点:

  • Entity(named:in:) 从 RealityKit Content Package 加载场景,返回的是 Entity 类型
  • content.add(scene) 把加载的实体添加到 RealityView 的内容中
  • content.entities 可以访问已添加的所有实体
  • transform.scale 接受 SIMD3<Float>,用数组字面量 [x, y, z] 简洁设置
  • 手势链式调用:先创建 TapGesture(),再用 .targetedToAnyEntity() 指定目标,最后用 .onEnded 处理事件

Glass Background Effect

visionOS 上的 UI 控件需要玻璃背景效果来保证可读性和交互性:

VStack {
    Toggle("Enlarge RealityView Content", isOn: $enlarge)
        .toggleStyle(.button)
}
.padding()
.glassBackgroundEffect()

关键点:

  • .glassBackgroundEffect() 给视图添加平台标志性的毛玻璃背景
  • 这个效果会根据背后的 3D 内容自动调整透明度和模糊程度
  • 所有可交互控件都应该包裹在这个效果中

打开 Immersive Space

struct ContentView: View {
    @Environment(\.openImmersiveSpace) var openImmersiveSpace

    var body: some View {
        Button("Open") {
            Task {
                await openImmersiveSpace(id: "ImmersiveSpace")
            }
        }
    }
}

关键点:

  • openImmersiveSpace 是环境值,需要从 @Environment 获取
  • 它是异步的,用 await 等待空间打开完成
  • 传入的 ID 必须与 App 中声明的 ImmersiveSpace ID 匹配
  • 必须在 Task 中调用,因为 SwiftUI 的 body 不是异步上下文

实体点击动画

.gesture(TapGesture().targetedToAnyEntity().onEnded { value in
    var transform = value.entity.transform
    transform.translation += SIMD3(0.1, 0, -0.1)
    value.entity.move(
        to: transform,
        relativeTo: nil,
        duration: 3,
        timingFunction: .easeInOut
    )
})

关键点:

  • value.entity 是被点击的具体实体,不是整个 RealityView
  • transform.translationSIMD3<Float> 类型的位移向量
  • move(to:relativeTo:duration:timingFunction:) 是 RealityKit 的内置动画方法
  • relativeTo: nil 表示在世界坐标系中移动
  • .easeInOut 让动画有自然的加速减速效果

Xcode Preview 配置

预览 ImmersiveView 时,默认的预览边界会裁剪掉超出范围的内容。需要添加 .previewLayout(.sizeThatFits)

#Preview {
    ImmersiveView()
        .previewLayout(.sizeThatFits)
}

关键点:

  • #Preview 宏替代了旧的 PreviewProvider 协议
  • .sizeThatFits 让预览根据内容自适应大小,不裁剪边界外的 3D 内容
  • 在 Simulator 中可以用同样的导航控制(look、pan、orbit、move)来观察场景

核心启发

1. 3D 产品展示器 做什么:做一个电商 App,让用户在 Volume 中 360 度查看商品模型,点击部件显示详情。 为什么值得做:RealityView + targetedToAnyEntity 让 3D 交互变得和 2D 一样简单,不再需要复杂的射线检测代码。 怎么开始:用 Reality Composer Pro 导入商品 USDZ 模型,给每个可交互部件添加 CollisionComponent 和 InputTargetComponent,在 SwiftUI 中用 .targetedToAnyEntity() 绑定点击手势,弹出详情面板。

2. 沉浸式数据可视化 做什么:把复杂数据集(如城市人口密度、股票走势)做成 3D 沉浸式图表,用户走进数据中探索。 为什么值得做:Immersive Space 让用户可以用身体移动来探索数据,比 2D 屏幕更直观。 怎么开始:用 RealityKit 程序化创建柱状图或散点图实体,放入 ImmersiveSpace 中,用 openImmersiveSpace 从 Window 中的按钮触发进入。

3. 虚拟空间导览 做什么:做一个博物馆或房产导览 App,从 Window 浏览列表,点击进入 Immersive Space 身临其境地参观。 为什么值得做:三种场景类型的组合让 App 可以平滑过渡从信息浏览到沉浸体验。 怎么开始:Window 中用 SwiftUI List 展示展品,选中后打开对应 ID 的 ImmersiveSpace,每个空间加载不同的 Reality Composer Pro 场景。

4. 手势驱动的 3D 创作工具 做什么:做一个简单的 3D 场景编辑器,用户点击实体可以移动、旋转、缩放。 为什么值得做:Entity targeting 和 RealityKit 动画 API 的组合让构建交互式 3D 编辑器变得可行。 怎么开始:给实体添加手势识别,在 .onEnded 中修改 transform,用 move(to:)orientation 实现平滑动画过渡。

关联 Session

评论

GitHub Issues · utterances