大多数浏览器和
Developer App 均支持流媒体播放。
-
将 SwiftUI 提升至新的维度
准备好为你的 VisionOS App 添加深度和维度。了解如何使用容积将三维对象引入你的 App,了解 Model 3D API,并了解如何放置内容并为内容添加动画。我们还将向你展示如何在 RealityView 中使用 UI attachments 并在内容中支持手势。
章节
- 0:00 - Introduction
- 1:49 - Volumes
- 2:57 - 3D views and layout
- 7:46 - RealityView
- 10:55 - 3D gestures
资源
相关视频
WWDC23
-
下载
♪ 悦耳的器乐嘻哈 ♪ ♪ 大家好 我是 Mark 一名 SwiftUI 工程师 我很高兴邀请你和我探索 使用 SwiftUI 突破平面进入空间 为了演示 SwiftUI 如何在一个新的维度中运作 以及 SwiftUI 如何 与 RealityKit 在系统中配合 来获得出色体验 我们会讨论一些 API 这些 API 对我们构建的 示例 App World 有帮助 World 展现了几种 App 在系统上 能接受的不同呈现方式 从熟悉的窗口到新的空间容器 为全空间提供了有界 3D 体验 而这使得无界虚拟内容得以显示 无论是在你的 2D 窗口 App 中 添加一点 3D 内容 还是充分利用全空间 创建沉浸式 3D 体验 我们将讨论的 API 大多都适用 因为空间容器提供了一种 使用 SwiftUI 探索 3D 领域的出色方式 所以 我们将重点关注 将空间容器作为容器的使用 想要进一步了解 关于 SwiftUI 内容其他容器的信息 请查看讲座 “将窗口 App 提升至空间计算” 和 “使用 SwiftUI 突破窗口的限制” 我们讲解完空间容器的基础知识后 将讨论你如何能使用立体视图和布局 来创建并放置 3D 内容 如何将 SwiftUI 视图 集成到新的 RealityView 中 以及如何使用 3D 手势 将所有东西联系在一起 在讲解这些内容之前 让我们先谈谈空间容器是什么 以及它们如何 帮助我们开始探索 3D 领域 World 使用空间容器 来突出其 3D 内容 此场景没有主玻璃窗口 相反 App 将其 3D 内容 直接放入场景中 前面有个控制面板 但 3D 内容才是这里的主角 空间容器为你提供一个 固定大小的容器 它不像窗口那样 会根据与你之间的距离动态缩放 它们在任何距离下 大小都保持不变 空间容器水平对齐并支持 从任何角度进行查看 空间容器是一种无需占用整个空间 就能在 App 中 显示 3D 内容的出色方式 其创建过程也非常简单 只需在创建新场景 如窗口组时 使用新的立体窗口样式 你就会自动获得所有功能 现在我们已经设置好了空间容器 让我们往里面添加一些内容 为了实现这一点 RealityKit 提供了 一个新的 Model3D API 它是一个 SwiftUI 视图 能帮助你简单地加载 丰富的 3D 资源 比如 USDZ 同时还能提供不同的阶段来处理 资源加载生命周期的不同部分 我们可以把 Model3D 看做是 AsyncImage 的对应物 它可以处理 加载复杂几何体的所有工作 同时保证你的 App 流畅运行 让我们使用 Model3D 来呈现 World App 中的 一个模型:月球 现在 我已经将月球 USDZ 文件 添加到了我的项目中 我可以直接将名称 传入 Model3D 初始化定式 现在我们可以处理模型的阶段 这并不是月球的阶段 (月相) 它实际上指的是资源的加载状态 它有好几种不同的状态 我们可以进行切换 在模型加载出来之前 我可以显示一些文本 或另一个 UI 来告诉用户 内容还未准备好 这里我会使用 ProgressView 如果模型加载失败 我可以使用 error.localizedDescription 来显示一条错误信息 如果加载成功 我就可以在 UI 中使用模型
和图像相似 我需要使用 resizable 修饰符 来告诉布局系统 模型大小可以根据其可用空间 进行调整 我希望模型大小适合其可用空间 所以我将使用 scaledToFit 修饰符 现在我的 App 中 就有一个异步加载的月球模型了 让我们继续完善这个示例 来探索 SwiftUI 中 更多的 3D 功能 让我们看看 World App 中 出现的一些其他的模型 我希望它们可以一同呈现出来 我们可以更改刚才写的 MoonView 的用途 来处理任何 USDZ 文件的显示 我们重命名一下 传入要加载的 USDZ 文件的名称 现在我可以换掉“Moon”字符串 现在 我们创建一个 数据结构来代表天体对象 我将给每个天体一个名称和大小 我将列出项目中几个不同的对象: 地球、月球和太阳 现在我可以使用 ForEach 在 HStack 中显示每个对象 对每个对象 我可以使用 我们刚刚用对象名称创建的 CelestialObjectView 让我们使用用对象大小创建的 frame 修饰符 来改变每个模型的大小
我们的模型从前面看很棒 但要记住这是 3D 模型 改变视角可以让我们看到 我们的对象是背面对齐的 好像它们的边界框 与其后面的一个平面齐平一样 这是 SwiftUI 中 3D 内容的默认对齐方式 如果我们希望更改对齐 可以在 frame depth 修饰符中 指定要使用的对齐深度 我可以传入对象大小来作为深度 并指定我希望模型以正面进行对齐 而不是背面 现在视图都以正面对齐了 现在 我希望给每个对象一个标签 我可以通过给每个 Model3D 添加叠图来实现 在其中 我将创建一个 带有 glassBackgroundEffect 的文本标签 来确保其总是可读 我也会将这些标签与模型底部对齐 这样就不会遮挡内容 现在的显示看起来不错 但感觉有点静态 最后 让我们探索 SwiftUI 中新增的一些 几何效果 我可以使用 TimelineView 来设置时间变化的动画效果
现在 我将使用新的 Rotation3DEffect 为视图添加一点旋转效果 我将根据当前日期并使用缩放因子 创建一个旋转效果 并赋予其一个角度
然后 我会让对象围绕 y 轴旋转 就这样 我们的对象开始旋转了 Model3D 特别适用于在视图层次中 加载及显示简单的资源 但对于涉及到的更多 3D 模型、场景或体验来说 RealityView 是解锁 App 中 RealityKit 强大功能的 SwiftUI 切入点 事实上 World App 使用 RealityView 来拆分 使用 RealityKit Entity-Component 系统的 更复杂的可视化 这使我们能渲染单个模型 并可以使用高级渲染效果 例如光照和轨道路径 感谢新的 RealityView SwiftUI 和 RealityKit 能在平台上紧密配合使用 想要进一步了解关于 RealityView 的信息 和更多 RealityKit 新功能 请查看 “使用 RealityKit 构建空间体验” 和“使用 RealityKit 增强你的空间计算 App” 现在 我们来谈谈你如何 使用新的 attachments API 以在 RealityView 中 充分利用 SwiftUI attachments 让你能将 带有标识符的 SwiftUI 视图 与你在 RealityView 中 使用的实体进行配对 attachments 非常适合用来添加注释 或编辑与特定实体相关的预设用途
如果你在 SwiftUI 中 使用过 Canvas API 你可能就会觉得 attachments 并不陌生 两者之间的关键不同在于 attachments 是实况视图而不是快照 这意味着它们可以回应状态改变、 运行动画及处理手势 让我们来探索 World App 中的 attachments 为此 我们会添加一些额外的功能 这些功能让我能放置一些标记 以在全世界追踪我收藏的地点 在地球视图中 我已经创建了一些状态 来追踪我收藏的地点 这些地点存放在一个数组里面 每个地点有一个名字和位置 我可以为每个地点 创建一些带有其名字的文本 我还可以为其添加 glassBackgroundEffect 这样文本就总是易读的 现在 我将添加一个 tag 这样之后 我就可以在 RealityView 中引用 tag 可以是任意哈希值 但我将使用 为每个地点添加的唯一标识符 现在 我可以使用指定的 tag 来查找托管 每个 attachment 视图的实体 我可以将此实体添加到 RealityView 内容中 然后 我可以用 lookAt 函数 来沿着地球的表面 确定标签的位置和方向 现在 已经有几个 有趣的地点被标记出来了 但我们还不能太依赖这款 App 我还有一些想法 可以让 App 更上一层楼 现在 我们了解了 如何使用 attachments 放置 3D 内容 并在 RealityView 中 充分利用 SwiftUI 现在 我们来谈谈如何与内容交互 平台将你已熟悉的手势引入了 第三维度中 支持手部、眼睛 以及新的触控板机制 让我们使用这些新功能 使 World App 更完善 我们使用“收藏地点”扩展 进行的还不错 但是 我对标记出来的地点的数量 并不是很满意 让我们通过在地球上 使用轻点手势来添加更多地点 在我们开始之前 先来讨论一下 如何配置实体来进行输入 假设我们正在用内容 配置 RealityView 这里 我已经添加了地球模型 实体层次需要 InputTargetComponent 以在 RealityView 中接收输入 如果该组件添加到实体中 那么该实体的所有后代实体 都可以接受输入 除非另有指定 在 RealityKit 中 CollisionComponent 用来定义实体可交互区域的形状 让我们在地球模型上使用球体 这样我们就可以在地球表面 获得准确的交互点 这就是实体在 RealityView 中 处理 SwiftUI 手势 所需要的全部操作 在这里 我可以将 SpatialTapGesture 添加到 RealityView 中
但为了更轻松地 在 RealityKit 内容中 使用 SwiftUI 手势 我们添加了新的手势修饰符 targetedToEntity 我可以使用它来专门处理 我的 earthEntity 如果没有轻点出现在 该实体或其后代上 手势操作就会失败 现在 让我们来处理手势值 SpatialTapGesture 有一个新 location3D 属性 此属性让我们能在地球表面 获得准确的轻击点 3D 位置在 RealityView 的 本地 SwiftUI 坐标空间中 以点而不是米为单位 为了确定希望放置新标签的地点 我们将需要将位置转换为 RealityView 的场景 通过将一些坐标空间转换助手 添加到手势值本身当中 targetedToEntity 修饰符 使转换过程变得超级简单 我们可以使用此转换方法将 SwiftUI 本地空间转换为 场景的坐标空间 最后 我可以使用刚计算出的位置 来为新地点添加数据 我也将位置稍微放大一点 标签就会稍微悬浮在地球表面上方
现在 我们只要轻点 就能在地球上添加更多 收藏的地点了 但现在我们有个问题: 我们需要发现更多地方! 为此 让我们启动一个卫星 它可以帮助我们 在世界上找到更多精彩的地方 在地球上添加卫星模型的一个方式 是用 RealityKit 加载一个模型 但让我们来使用一些 了解过的其他方法 我可以添加 Model3D 作为一个 attachment 指定一个 frame 可以很轻松地将卫星模型 调整到合适的大小 我也将给模型一个 tag 这样我就能在 RealityView 中 引用它 就像带有标签一样 我需要将模型添加到 RealityView 中 现在 让我们定义一个 返回 3D 变换的手势 它能让我们定义卫星的大小、旋转 以及位置 我会使用 DragGesture 开始拖动 为了将拖动手势变换为转换 我会使用 map DragGesture 有好几个 处理 3D 操作的新属性 我们可以用 DragGesture 的 新 translation3D 属性 来获取自拖动开始的移动量 现在 我将创建转换 我可以将 translation 传入初始化定式 并从 map 返回 现在 我可以使用刚写的操作手势 来转换我的卫星 我会用 updating 修饰符 来追踪手势何时处于活动状态 我可以使用该状态 来在交互过程中缩小所有标签 这样标签就不会遮挡我们的地球 使用 updating 追踪所有短暂的手势状态 是很重要的 因为它可以保证在手势操作失败时 手势状态会自动重置 我的手势值改变时 我可以设置状态的新转换 然后使用 offsets 修饰符 来放置卫星模型 我也将使用弹簧动画 来对转换改变进行动画操作 这样 当我们释放卫星时 它将以动画形式返回到初始位置 现在我们可以随意拖动卫星了 这是个很好的开始 但我们需要 注意一些细节 既然所有东西都已经连接起来了 让我们来添加一些缩放操作 我会添加一个 MagnifyGesture 它能与拖动同时进行识别 我也将添加新的 RotateGesture3D 它能测量用户 手部的自然 3D 旋转
我将把这些新值传入到转换中 最后 我需要更新实体的 旋转和缩放 为此 我将使用 rotation3DEffect 和 scaleEffect 成功了! 我们现在可以随意拖动、 缩放并旋转我们的卫星实体 卫星看起来已经准备好出发了 我们添加的手势能在你期待的 所有输入设备和模型上使用 包括手部直接交互、 间接捏合、触控板以及辅助功能 使用熟悉的 SwiftUI 手势 配合新的 targetedToEntity 修饰符 你能在复杂的实体层次结构中 快速构建交互 我们现在准备好 使用卫星来探索星球了 但现在轮到你在你的 App 中 探索 SwiftUI 的新 3D 功能了 空间容器和全空间 这样的新场景类型可以让你 以全新的方式考虑 App 的设计 借助 SwiftUI 中扩展的 强大布局和渲染系统 SwiftUI 不仅能在 iOS、macOS、 Apple tvOS 以及 watchOS 上构建 App 也能在全新平台上构建 App 新的 attachments API 开启了将 SwiftUI 集成到 3D 场景中的出色新方式 最后 我们探索了 如何使用 SwiftUI 中 熟悉且强大的手势 让用户通过手部即时获得故事体验 使用 SwiftUI 和 RealityKit 你将拥有一段 App 构建的精彩旅程 去突破 2D 平面的限制 我们才刚刚开始 欢迎来到我们的平台! ♪
-
-
3:35 - MoonView
struct MoonView { var body: some View { Model3D(named: "Moon") { phase in switch phase { case .empty: ProgressView() case let .failure(error): Text(error.localizedDescription) case let .success(model): model .resizable() .scaledToFit() } } } }
-
17:26 - Manipulation Gesture
// Gesture combining dragging, magnification, and 3D rotation all at once. var manipulationGesture: some Gesture<AffineTransform3D> { DragGesture() .simultaneously(with: MagnifyGesture()) .simultaneously(with: RotateGesture3D()) .map { gesture in let (translation, scale, rotation) = gesture.components() return AffineTransform3D( scale: scale, rotation: rotation, translation: translation ) } } // Helper for extracting translation, magnification, and rotation. extension SimultaneousGesture< SimultaneousGesture<DragGesture, MagnifyGesture>, RotateGesture3D>.Value { func components() -> (Vector3D, Size3D, Rotation3D) { let translation = self.first?.first?.translation3D ?? .zero let magnification = self.first?.second?.magnification ?? 1 let size = Size3D(width: magnification, height: magnification, depth: magnification) let rotation = self.second?.rotation ?? .identity return (translation, size, rotation) } }
-
-
正在查找特定内容?在上方输入一个主题,就能直接跳转到相应的精彩内容。
提交你查询的内容时出现错误。请检查互联网连接,然后再试一次。