Highlight
SwiftUI 在 visionOS 上通过 Volume 容器、Model3D API、RealityView attachments 和 3D 手势,让开发者可以用熟悉的声明式语法构建有界 3D 体验,无需接管整个空间即可展示和操作三维内容。
核心内容
Volume:固定尺寸的 3D 容器
Window 会根据距离动态缩放,Volume 不同——它保持固定尺寸,无论离你多远,1 米宽的 Volume 始终是 1 米宽。Volume 水平对齐,支持从任意角度观看。
创建 Volume 只需要在 WindowGroup 上应用新的窗口样式:
@main
struct WorldApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.windowStyle(.volumetric)
}
}
关键点:
.windowStyle(.volumetric)将普通窗口变为 Volume 容器- Volume 内没有玻璃背景,3D 内容直接放入场景
- 内容可以配合控制面板一起展示
(02:14)
Model3D:3D 资源的 AsyncImage
Model3D 是加载 USDZ 等 3D 资源的 SwiftUI 视图,设计理念与 AsyncImage 类似,通过 phase 管理加载生命周期:
struct MoonView: View {
var body: some View {
Model3D(named: "Moon") { phase in
switch phase {
case .empty:
ProgressView()
case .failure(let error):
Text(error.localizedDescription)
case .success(let model):
model
.resizable()
.scaledToFit()
}
}
}
}
关键点:
Model3D(named:)从资源包加载指定名称的 USDZ 文件phase有三种状态:empty(加载中)、failure(失败)、success(成功).resizable()告知布局系统模型可以根据可用空间调整大小.scaledToFit()让模型在可用空间内保持比例缩放
(03:35)
3D 布局与深度对齐
多个 3D 模型并排时,默认使用背面对齐(back face aligned),即所有模型的背面齐平:
struct CelestialObjectView: View {
let name: String
let size: CGFloat
var body: some View {
Model3D(named: name) { phase in
switch phase {
case .success(let model):
model
.resizable()
.scaledToFit()
.frame(width: size, height: size, depth: size)
.frame(depth: size, alignment: .front)
default:
EmptyView()
}
}
}
}
关键点:
.frame(width:height:depth:)设置 3D 视图的三维尺寸.frame(depth:alignment:)控制深度方向的对齐方式.front让模型正面对齐,.back(默认)让背面对齐- 从斜侧面观看时,对齐方式的差异会很明显
(05:43)
3D 动画效果
SwiftUI 提供了 Rotation3DEffect 用于三维旋转:
TimelineView(.animation) { timeline in
CelestialObjectView(name: object.name, size: object.size)
.rotation3DEffect(
Angle(degrees: timeline.date.timeIntervalSinceReferenceDate * 10),
axis: .y
)
}
关键点:
TimelineView(.animation)提供持续更新的时间源.rotation3DEffect(axis:)围绕指定轴旋转视图- 这里围绕 y 轴旋转,模拟天体自转效果
(07:09)
详细内容
RealityView Attachments:将 SwiftUI 视图嵌入 3D 场景
RealityView 的 attachments API 让你把 SwiftUI 视图绑定到 RealityKit 实体上。这些视图是活的——可以响应状态变化、运行动画、处理手势。
RealityView { content, attachments in
// 加载地球模型
let earth = try! Entity.load(named: "Earth.usdz")
content.add(earth)
// 为每个地点创建标签附件
for place in favoritePlaces {
if let label = attachments.entity(for: place.id) {
content.add(label)
// 将标签定位到地球表面
label.lookAt(earth, from: place.location, relativeTo: earth)
}
}
} attachments: {
ForEach(favoritePlaces) { place in
Text(place.name)
.glassBackgroundEffect()
.tag(place.id)
}
}
关键点:
attachments参数提供对标记视图的访问- 视图通过
.tag()标记,在 RealityView 中通过attachments.entity(for:)获取对应的实体 .glassBackgroundEffect()确保标签在任何背景下可读lookAt(from:relativeTo:)将附件定位并朝向指定位置- 附件视图支持完整的 SwiftUI 功能,包括状态更新和手势
(08:39)
为实体配置输入接收
要让 RealityView 中的实体响应手势,需要添加两个组件:
RealityView { content in
let earth = try! Entity.load(named: "Earth.usdz")
// 添加输入目标组件,允许接收输入
earth.components.set(InputTargetComponent())
// 添加碰撞组件,定义交互区域的形状
earth.components.set(
CollisionComponent(shapes: [.generateSphere(radius: 1.0)])
)
content.add(earth)
}
关键点:
InputTargetComponent()标记实体及其后代可以接收输入CollisionComponent定义交互区域的几何形状- 这里使用球体碰撞体匹配地球模型的形状
- 碰撞体精度影响手势定位的准确性
(11:32)
空间点击手势
SpatialTapGesture 提供 3D 点击位置:
RealityView { content in
// ... 配置地球实体
}
.gesture(
SpatialTapGesture()
.targetedToEntity(earthEntity)
.onEnded { value in
// 获取 3D 点击位置(SwiftUI 本地坐标空间,单位是 points)
let location3D = value.location3D
// 转换为场景坐标空间
let sceneLocation = value.convert(location3D, from: .local, to: earthEntity)
// 在点击位置上方添加新地点
let newPlace = FavoritePlace(
name: "New Discovery",
location: sceneLocation * 1.05 // 稍微浮出表面
)
favoritePlaces.append(newPlace)
}
)
关键点:
.targetedToEntity()将手势限定在指定实体上,点击其他区域手势失败value.location3D获取点击的 3D 坐标value.convert(...)在坐标空间之间转换位置- 坐标单位是 points,需要转换为场景空间使用
(12:15)
组合 3D 操作手势
将拖拽、缩放和旋转组合成一个完整的手势:
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
)
}
}
// 提取手势组件的辅助方法
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)
}
}
使用手势控制实体变换:
@State private var isDragging = false
RealityView { content, attachments in
// ... 配置内容
} update: { content, attachments in
if let satellite = content.entities.first(where: { $0.name == "satellite" }) {
satellite
.gesture(
manipulationGesture
.updating($isDragging) { _, state, _ in
state = true
}
.onChanged { value in
satellite.transform = Transform(value)
}
)
}
}
关键点:
DragGesture的translation3D属性提供 3D 平移量RotateGesture3D测量无约束的 3D 旋转simultaneously(with:)将多个手势组合为同时识别的手势组AffineTransform3D封装 3D 变换的平移、旋转和缩放.updating跟踪手势的瞬时状态,手势失败时自动重置- 手势支持直接手部交互、间接捏合、触控板和辅助功能
(17:26)
核心启发
-
做什么:创建一个 3D 产品展示应用,让用户可以从任意角度查看商品
- 为什么值得做:Volume 容器让 3D 内容可以在 Shared Space 中与其他应用共存。Model3D 异步加载 USDZ 模型,配合
.resizable()和.scaledToFit()自动适配布局 - 怎么开始:用
.windowStyle(.volumetric)创建 Volume,用 Model3D 加载产品模型,添加 Rotation3DEffect 让模型自动旋转展示
- 为什么值得做:Volume 容器让 3D 内容可以在 Shared Space 中与其他应用共存。Model3D 异步加载 USDZ 模型,配合
-
做什么:开发一个带标注的 3D 地图或解剖图应用
- 为什么值得做:RealityView attachments 让 SwiftUI 视图可以精确附着在 3D 模型的特定位置。标签可以响应状态更新,支持玻璃背景确保可读性
- 怎么开始:在 RealityView 的 attachments 闭包中创建 Text 标签,用
.tag()标记,在 RealityView 闭包中用attachments.entity(for:)获取并定位到模型表面
-
做什么:构建一个支持手势操作的 3D 设计工具
- 为什么值得做:组合手势(拖拽+缩放+旋转)让用户可以用自然的手部动作操纵 3D 对象。
targetedToEntity确保手势精确作用于目标对象 - 怎么开始:定义
DragGesture.simultaneously(with: MagnifyGesture).simultaneously(with: RotateGesture3D)组合手势,用.targetedToEntity()限定目标,在.onChanged中更新实体的 transform
- 为什么值得做:组合手势(拖拽+缩放+旋转)让用户可以用自然的手部动作操纵 3D 对象。
-
做什么:制作一个 3D 拼图或组装教学应用
- 为什么值得做:SpatialTapGesture 的
location3D提供精确的 3D 点击位置。通过InputTargetComponent和CollisionComponent可以为复杂模型配置准确的交互区域 - 怎么开始:为每个可交互部件添加碰撞组件,使用
SpatialTapGesture().targetedToEntity()捕获点击,根据点击位置判断用户选择了哪个部件
- 为什么值得做:SpatialTapGesture 的
关联 Session
- Elevate your windowed app for spatial computing — 将现有 SwiftUI 应用适配到 visionOS,了解窗口和 ornament
- Go beyond the window with SwiftUI — 使用 ImmersiveSpace 创建无边界的沉浸式体验
- Build spatial experiences with RealityKit — RealityKit 实体-组件系统、渲染和动画详解
评论
GitHub Issues · utterances