Highlight
visionOS 26 让 SwiftUI 和 RealityKit 真正合体:Model3D 直接播放动画、RealityKit Entity 接住 SwiftUI 视图与手势、坐标系双向贯通。
核心内容
过去在 visionOS 上展示一个 3D 机器人,开发者经常被迫在 Model3D 和 RealityView 之间二选一。Model3D 一行代码就能塞进 SwiftUI 布局,但播不了动画、加不了粒子;想要换装、加碰撞、做粒子发射,就得切到 RealityView,然后立刻面对一个新麻烦——RealityView 默认会把所有可用空间吃干净,旁边并排的 NameSign 立刻被推到屏幕一边。
visionOS 26 把这条路彻底打通。Apple 给 Model3D 加上了 Model3DAsset,可以直接读取模型自带的动画轨道,配合新的 ConfigurationCatalog 切换不同外观;想升级到 RealityView 时,新加的 .realityViewLayoutBehavior(.fixedSize) 让它像 Model3D 一样紧贴模型边界。RealityKit 这边也开放了三个 SwiftUI 风格的新组件——ViewAttachmentComponent、GestureComponent、PresentationComponent,外加新的 ManipulationComponent 让用户用单手或双手抓起虚拟物体。Entity 现在是 Observable 的,AnimationPlaybackController 也是,SwiftUI 视图可以直接 @Bindable 这些 RealityKit 对象,状态在两边自由流动。
详细内容
Model3D 加载动画与控制器(03:34)。Model3DAsset 异步加载模型文件,再交给 Model3D 显示;选定的动画通过 animationPlaybackController 暴露出来,配合 SwiftUI 的 Slider 做时间擦除。
struct RobotView: View {
@State private var asset: Model3DAsset?
var body: some View {
if asset == nil {
ProgressView().task { asset = try? await Model3DAsset(named: "sparky") }
} else if let asset {
VStack {
Model3D(asset: asset)
AnimationPicker(asset: asset)
if let animationController = asset.animationPlaybackController {
RobotAnimationControls(playbackController: animationController)
}
}
}
}
}
关键点:
Model3DAsset(named:)是异步初始化器,要放在.task里等待。asset == nil阶段用 ProgressView 兜底,加载完才切到真正的 Model3D。asset.animationPlaybackController由 Model3DAsset 在选中动画后生成,外部不要自己 new。- AnimationPlaybackController 在 visionOS 26 已经是 Observable,可直接当 view model 用。
用 @Bindable 把 SwiftUI 控件接到 RealityKit 控制器(04:32)。
struct RobotAnimationControls: View {
@Bindable var controller: AnimationPlaybackController
var body: some View {
HStack {
Button(controller.isPlaying ? "Pause" : "Play") {
if controller.isPlaying { controller.pause() }
else { controller.resume() }
}
Slider(
value: $controller.time,
in: 0...controller.duration
).id(controller)
}
}
}
关键点:
@Bindable var controller把 RealityKit 类直接挂成 SwiftUI 的可观察数据源。controller.isPlaying变化会触发视图重算,按钮文案自动同步。$controller.time把 Slider 的拖动直接写回控制器,做到时间擦除。.id(controller)在动画切换时强制 Slider 重建,避免上一个动画的范围残留。
从 Model3D 平滑迁移到 RealityView(07:25)。RealityView 默认拿走全部可用空间,加上 .fixedSize 后会用首次布局时 Entity 的视觉边界来定尺寸。
struct RobotView: View {
let url: URL = Bundle.main.url(forResource: "sparky", withExtension: "reality")!
var body: some View {
HStack {
NameSign()
RealityView { content in
if let sparky = try? await Entity(contentsOf: url) {
content.add(sparky)
}
}
.realityViewLayoutBehavior(.fixedSize)
}
}
}
关键点:
.fixedSize、.flexible、.centered三选一,分别对应紧贴边界、占满空间、居中放置。- 尺寸只在
make闭包结束后评估一次,后续 Entity 体积变化不会重新撑大 RealityView。 - 这三个修饰符只移动 RealityView 的原点,不会改写 Entity 自身的位置或缩放。
ManipulationComponent 让 Entity 被双手抓取(13:18)。configureEntity 一行代码会同时挂上 ManipulationComponent、CollisionComponent、InputTargetComponent、HoverEffectComponent。
RealityView { content in
let sparky = await loadSparky()
content.add(sparky)
ManipulationComponent.configureEntity(
sparky,
hoverEffect: .spotlight(.init(color: .purple)),
allowedInputTypes: .all,
collisionShapes: myCollisionShapes()
)
}
关键点:
configureEntity是静态便利方法,自动补齐交互所需的全部组件。hoverEffect控制凝视/悬停时的视觉反馈,这里用紫色聚光灯。allowedInputTypes: .all同时允许直接触碰和 gaze + pinch 间接输入。collisionShapes决定可抓取范围,传 nil 则用自动生成。- 想换抓取/放下的音效,把
audioConfiguration设为.none,再订阅ManipulationEvents.DidHandOff自行播放。
ViewAttachmentComponent + GestureComponent 让 Entity 长出 SwiftUI(17:10)。
struct AttachmentComponentAttachments: View {
var body: some View {
RealityView { content in
let bolts = await loadAndSetupBolts()
let attachment = ViewAttachmentComponent(
rootView: NameSign("Bolts"))
let nameSign = Entity(components: attachment)
place(nameSign, above: bolts)
bolts.components.set(GestureComponent(
TapGesture().onEnded {
nameSign.isEnabled.toggle()
}
))
content.add(bolts)
content.add(nameSign)
}
.realityViewLayoutBehavior(.centered)
}
}
关键点:
- ViewAttachmentComponent 直接用任意 SwiftUI 视图作 rootView,不再需要 RealityView 初始化时声明 attachments。
Entity(components: attachment)把组件作为初始化参数注入,省掉一次 set。- GestureComponent 接收标准 SwiftUI 手势,回调里的坐标默认就是 Entity 的本地空间。
- 用 GestureComponent 时,目标 Entity 仍需要 InputTargetComponent 和 CollisionComponent,否则收不到事件。
核心启发
-
做什么:把现有 Model3D 升级成 Model3DAsset 驱动的播放器
- 为什么值得做:原本静态展示的 3D 资源能直接复用美术导出的动画,用户能换装、能拖时间轴。
- 怎么开始:把模型导出为带动画的 reality 文件,用
Model3DAsset(named:)加载,配上 Picker + Slider 做最小可玩 demo。
-
做什么:在 visionOS 应用里用
.realityViewLayoutBehavior(.fixedSize)替换”撑满空间”的 RealityView- 为什么值得做:很多布局错位问题都源于 RealityView 默认抢空间,加上一个修饰符就能让它和周围 SwiftUI 控件并排。
- 怎么开始:搜索项目里所有 RealityView,把展示型用例都加上
.fixedSize,让交互式场景保留.flexible。
-
做什么:给可被抓取的 3D 物体加一行
ManipulationComponent.configureEntity- 为什么值得做:四个常用组件一次配齐,加上系统提供的握取音效,几乎零成本就能让虚拟物体响应双手交互。
- 怎么开始:先在原型里对核心展示物体启用,订阅
ManipulationEvents.DidUpdateTransform把位姿写回业务状态。
-
做什么:用 ViewAttachmentComponent 把信息卡、按钮、说明文字直接挂在 Entity 上
- 为什么值得做:SwiftUI 视图作为组件挂载之后,可以跟随 Entity 一起被场景管理、被手势响应、被 Observable 状态驱动。
- 怎么开始:把当前用 RealityViewAttachments 写的 nameSign / tooltip 改成 ViewAttachmentComponent,再尝试挂上 GestureComponent 让它可点。
关联 Session
- Build a SwiftUI app with the new design — Liquid Glass 在 SwiftUI 中的落地与 visual refresh 配合点。
- Build a UIKit app with the new design — UIKit 与新设计系统的协同,可与 SwiftUI 视角对照阅读。
- Explore prompt design & safety for on-device foundation models — 端上模型的 prompt 设计,给空间应用加 AI 能力时参考。
- Make a big impact with small writing changes — 跨平台一致的微文案策略,匹配 visionOS 上的新组件文本。
评论
GitHub Issues · utterances