RealityKit与空间心智模型
visionOS上的SwiftUI为您提供3D空间中的一个窗口。RealityKit则为您提供整个房间的其余部分。差异在于心智模型,而心智模型即架构。
初次接触visionOS的SwiftUI开发者往往会构建与iOS上相同的形态:一个WindowGroup,内部嵌套视图。结果是一块漂浮在空间中的扁平面板。对许多应用而言,面板就够用了。但面板周围的房间才是visionOS新增的部分,而房间正是RealityKit的领地。
RealityKit并非3D版的SwiftUI。两者的架构在形态上就不同,而非仅仅维度有别。SwiftUI是一棵基于值类型的视图树,顶层是结果构建器DSL,底层是观察系统;框架的职责是根据当前状态计算下一次渲染。RealityKit则是一个实体-组件-系统(ECS),其场景图以锚点为根,锚点将虚拟实体绑定到真实世界的参考点;框架的职责是在用户和世界移动时维护一个3D场景。同一个Swift项目可能同时使用两者,而二者的边界正是大多数空间设计错误发生之处。
本文梳理空间心智模型。该模型与SwiftUI窗口的五个具体差异,以及何时选用哪个框架的路由问题。
TL;DR
- RealityKit是一个实体-组件-系统(ECS)。实体是节点;组件是附加到节点上的类型化数据;系统在拥有特定组件组合的实体上运行逻辑。
- 锚点将虚拟实体绑定到真实世界的参考点。RealityKit通过
AnchoringComponent.Target(.world、.head、.hand(_:location:)、.plane(...)、.image(...)、.referenceObject(...))暴露锚定目标。在visionOS上,ARKit提供底层的锚点结构体(WorldAnchor、HandAnchor、ImageAnchor、ObjectAnchor、PlaneAnchor),这些结构体通过锚点更新流由ARKit上报。 - 3D场景不是视图树。渲染循环是连续的,场景图随时间变化,渲染层是GPU驱动的(底层为Metal),而非基于差异比对。
RealityView是SwiftUI桥梁。它将RealityKit场景置入SwiftUI视图树中;边界是单向的(SwiftUI承载RealityKit,反之不成立)。- 路由规则:如果用户想要窗口,交付SwiftUI。如果用户想要房间,交付RealityKit。需要兼顾两者的应用,会将
RealityView置入SwiftUI窗口中,并接受两部分需要显式协调。
与SwiftUI窗口的五大差异
一旦您把任何东西放到面板之外,形态就立刻改变。有五个差异至关重要。
1. 实体-组件-系统,而非视图-Body-状态
SwiftUI视图是一种值类型,带有body计算属性和由属性包装器支撑的状态。1 状态变化时,框架重新运行body;差异结果送入渲染器。
RealityKit实体是位于场景图中的引用类型对象。组件是附加到实体上的类型化结构体(ModelComponent、Transform、CollisionComponent、PhysicsBodyComponent,以及您自定义的Component类型)。2 系统是遵循System协议的类型;框架每帧运行一次每个已注册的系统,而System.update(context:)(在结构体上通常为mutating方法)正是该系统读写匹配查询的实体上的组件之处。
import RealityKit
let cube = Entity()
cube.components.set(ModelComponent(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .blue, isMetallic: false)]
))
cube.components.set(InputTargetComponent())
cube.components.set(CollisionComponent(shapes: [.generateBox(size: [0.1, 0.1, 0.1])]))
cube没有body。cube有一组组件。添加InputTargetComponent和CollisionComponent是让cube响应手势的方式;移除它们,手势就会径直穿过到其后的实体。添加PhysicsBodyComponent是让cube在重力下下落的方式;移除它,cube就会漂浮。组件的组合方式决定了实体的行为。
心智转变:在SwiftUI中,您将屏幕上应该呈现什么描述为状态的函数。在RealityKit中,您描述实体是什么(它的组件),由系统决定它会发生什么。
2. 锚点,而非坐标
SwiftUI视图的坐标系是窗口。位置0,0是左上角;位置以点为单位;视图的frame就是它的空间。窗口即宇宙。
RealityKit场景的坐标系取决于其锚点。锚定层有两面。RealityKit的AnchorEntity(由AnchoringComponent.Target驱动)是您将实体放置到其上的对象;ARKit的锚点结构体则是系统用来保持目标与真实世界同步的底层数据。3
您在AnchorEntity或AnchoringComponent内会用到的RealityKit锚定目标包括:
.world(transform:):由变换定义的真实世界空间中的点.head:锁定到用户的头部姿态;实体跟随用户的视线.hand(_:location:):锁定到特定的手部关节(手掌、指尖、手腕等).plane(...):锁定到检测到的水平或垂直表面(桌子、墙壁、地板).image(...)/.referenceImage(...):锁定到环境中识别出的2D图像.referenceObject(...):锁定到识别出的真实世界3D物体
在visionOS上,ARKit通过WorldAnchor、HandAnchor、ImageAnchor、ObjectAnchor和PlaneAnchor结构体提供底层锚点数据,这些数据通过ARKitSession锚点更新提供器交付。(身体追踪在Apple的身体追踪接口上仅支持iOS/iPadOS;visionOS不暴露.body作为RealityKit的锚定目标。)
锚点是让场景”真实”的关键。放置在.plane目标(用户桌面)上的虚拟棋盘,在用户绕其行走时会停留在桌面上;而放置在相对于.head目标固定坐标处的棋盘则会跟随用户的头部移动,感觉像幻觉。
心智转变:位置不是一个数字。位置是锚点是什么,以及实体相对于锚点在哪里。没有合理锚点的虚拟物体就是幻觉;锚点告诉用户该物体属于这个房间。
3. 连续渲染循环,而非基于差异比对
SwiftUI在状态变化时进行渲染。框架决定何时需要重新渲染,并计算最小化的树变更。两次渲染之间,屏幕是静态的。
RealityKit驱动的是基于帧的模拟和渲染循环。场景图随时间变化,因为物理系统、动画系统和输入处理器会更新实体的变换和组件值,而渲染器(Metal支撑)每帧绘制活动场景。每帧逻辑驻留在System.update(context:)中;该钩子是框架邀请您每个时钟周期变更场景的入口。4
心智转变:时间是场景的一部分。运行一次的SwiftUI视图body是可以的;而RealityKit实体需要考虑第N+1、N+2、N+3帧会发生什么。自定义System上的update(context:)方法是您编写每帧逻辑之处;您在update内变更的Component值,正是渲染器在下一轮读取的内容。
4. RealityView是桥梁,且为单向
SwiftUI视图组合其他SwiftUI视图。RealityKit实体组合其他RealityKit实体。两者之间的边界是RealityView,一种承载RealityKit场景的SwiftUI视图类型。5
import SwiftUI
import RealityKit
struct ContentView: View {
var body: some View {
RealityView { content in
// Build the scene
let cube = Entity()
cube.components.set(ModelComponent(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .blue, isMetallic: false)]
))
content.add(cube)
} update: { content in
// Mutate the scene in response to SwiftUI state changes
}
}
}
make闭包在视图首次出现时运行一次。update闭包在视图所依赖的SwiftUI状态变化时运行。在两个闭包内,您可以通过content参数访问RealityKit场景;您添加实体、变更变换、注册系统。
边界是单向的。SwiftUI承载RealityKit。RealityKit不承载SwiftUI;您不能把SwiftUI视图”放入”实体中,并期望它像SwiftUI子视图在父视图中渲染那样作为3D场景的一部分渲染。例外是附件(在RealityView的attachments:参数中):您声明命名的SwiftUI视图,以ViewAttachmentEntity值检索它们,然后像其他实体一样在3D场景中定位和缩放它们。5 附件不是嵌入在实体内的SwiftUI视图;它们是被包装为实体的2D SwiftUI表面,渲染器可将其放置于3D空间中。默认情况下,它们保持固定朝向;若希望它们朝向佩戴者,可在附件实体上附加BillboardComponent。
5. 3D中的手势是不同的
SwiftUI手势(.onTapGesture、DragGesture等)在屏幕空间中操作。系统知道手指相对于视图的位置;框架基于2D命中测试进行分发。
RealityKit手势在场景空间中操作。6 系统知道用户正在看哪里(注视射线)、用户的手在哪里(手部追踪关节),以及注视加上轻捏与哪个实体相交。分发模型是”用户看着这个实体并捏了一下”;相当于轻点。
要让实体接收手势,它需要InputTargetComponent和定义命中测试几何形状的CollisionComponent。没有InputTargetComponent,该实体对手势系统是不可见的;没有CollisionComponent,手势系统就没有形状可供命中测试。两者必须同时存在。
心智转变:手势目标不是屏幕区域。手势目标是显式选择接受输入的3D实体。场景的其余部分则是”用户可以视而不见的装饰”。
何时SwiftUI已足够
常见的visionOS应用并不需要RealityKit。三种仅用SwiftUI即为正解的模式:
没有空间内容的窗口形态应用。冥想计时器、笔记本、设置面板、聊天界面。该应用是您通过2D交互元素阅读或互动的信息。将其置于WindowGroup中并保持扁平就是正确选择。visionOS将SwiftUI窗口视为带有系统装饰的浮动玻璃面板;无需您写一行RealityKit代码,用户即可获得舒适的阅读体验。
在空间中组合扁平面板的多窗口应用。一个代码编辑器,分别为编辑器、终端和文档设有独立窗口。用户希望窗口在3D空间中排列(左、右、后),但每个窗口本身是一个SwiftUI视图。3D排列是操作系统的工作;面板是扁平的。
文档查看器、照片库、视频播放器。用户通过面板消费的内容。面板就是渲染表面;第三维度只是面板在房间中的空间位置。
规则:如果内容是2D的(文本、图像、视频、控件),正确的框架就是SwiftUI。第三维度只决定面板放置在何处,而非面板内部渲染什么。
何时必须使用RealityKit
SwiftUI不够用的场景:
用户可绕行的3D内容。用户桌面上的虚拟物体(模型车、雕塑、建筑)。该物体有体积;用户可以绕着它移动;该物体应与房间正确遮挡。正确的框架是RealityKit,锚定到.plane目标。
响应房间的空间UI。漂浮在真实键盘上方的按钮、附着在真实物体上的注释、沿真实墙壁铺设的虚拟卷尺。UI的位置由世界几何决定,而非由窗口的坐标空间决定。RealityKit锚点完成绑定;RealityView内的SwiftUI附件提供2D交互元素。
连续的空间模拟。一群飞鸟、滚过地板的球、流体模拟,任何场景状态随时间演变的内容。连续渲染循环是正确的工具;SwiftUI基于差异比对的渲染器要么会丢帧,要么会耗电。
手部追踪交互。捏取抓握、双手缩放、空中绘画。输入模型需要ARKit的HandTrackingProvider(配合HandAnchor更新)加上.hand(_:location:)锚点目标;SwiftUI不暴露该接口。
身体追踪AR。将用户的姿态映射到虚拟角色上、为健身应用追踪用户身体、识别真实世界物体。捕获和推理在ARKit(RealityKit的底层伴侣)中进行;RealityKit渲染结果。
规则:如果内容是3D的且驻留在房间中(体积化、锚定、模拟或手部驱动),正确的框架就是RealityKit。SwiftUI是其周边的装饰。
组合模式
大多数非平凡的visionOS应用最终都会两者并用。能够顺利交付的模式是:
- 应用的装饰部分(设置、导航、列表、表单、检查器面板)驻留在SwiftUI窗口中。
- 空间场景(用户操控的体积化内容)驻留在自身窗口或体积内的
RealityView中。 - 两者通过SwiftUI状态进行通信。SwiftUI面板中的按钮切换
@State布尔值;RealityView的update:闭包读取该布尔值并在场景中变更实体。 - 需要呈现给SwiftUI的RealityKit侧状态变化,通过
RealityView的make:闭包注册的回调(场景事件发布器上的subscribe(to:))来传递。7
struct GalleryView: View {
@State private var selectedSculpture: SculptureID?
var body: some View {
HStack {
// SwiftUI side: list of sculptures
List(allSculptures) { sculpture in
Button(sculpture.name) {
selectedSculpture = sculpture.id
}
}
.frame(width: 300)
// RealityKit side: 3D rendering of the selected sculpture
RealityView { content in
// Build initial scene
} update: { content in
guard let id = selectedSculpture else {
content.entities.removeAll()
return
}
// Mutate scene to show the selected sculpture
presentSculpture(id, in: content)
}
}
}
}
这种分工诚实地反映了哪个框架负责哪项工作。SwiftUI负责列表、按钮、布局、状态。RealityKit负责3D渲染、实体、连续模拟。状态作为单一的@State值跨越边界;两个框架都不会伸入对方内部。
我会在自己的技术栈中做哪些不同的设计
当您交付过空间场景后,有三个模式会变得容易识别:
先锚点,后实体。设计功能时,在确定几何形态之前先确定锚点。锚定到用户手部的虚拟乐器,与同一乐器锚定到桌面.plane目标,是不同的产品。锚点决定用户与物体的关系;几何形态只是实现细节。
用组件,不用子类。通过子类化Entity构建领域类型(如ChessPiece: Entity)很有诱惑力。但组合模式每次都胜过继承:一枚棋子是一个Entity,带有ChessPieceComponent(自定义数据:颜色、类型、位置)、ModelComponent(3D网格)、InputTargetComponent和CollisionComponent。新行为是新组件,而非新子类。
用系统处理横切逻辑。当十个实体需要相同行为(重力、碰撞响应、音频衰减、手势状态)时,编写一个System,使其作用于相关组件。该系统每帧针对所有匹配的实体运行一次。替代方案(把逻辑放在每个实体上)会产生ECS模式发明初衷就是为了避免的n×n帧问题。
何时RealityView不是正确答案
有几种情况,选用RealityView是错误的选择:
单个3D图像,无交互。静态3D logo或产品渲染图。改用SwiftUI的Model3D视图。8 Model3D是”加载USDZ并显示”的廉价路径;RealityView用于您构建并变更的场景。
带有简单AR叠加层的iOS应用。当AR体验只是更大iOS应用中的一项功能时,ARKit的ARView(较旧的接口)或RealityKit在iOS端的ARView集成往往是正确的选择。RealityView是Swift和SwiftUI原生的,在SwiftUI中运作良好;当应用其余部分是UIKit时,旧的ARView工作流有时更简单。
在面板上的2D绘图。白板、照片注释工具、扁平形状编辑器。正确的工具是Canvas(SwiftUI基于Metal的绘图表面)或MetalView。如果不在3D空间中构建,RealityView就是大材小用。
该模式对在visionOS 2+上交付应用的意义
三个要点。
-
RealityKit和SwiftUI是组合关系,不是合并关系。用SwiftUI做窗口形态的装饰和2D交互元素;用RealityKit做房间形态的3D内容。边界是
RealityView,且边界是单向的。 -
心智模型是ECS加锚点。实体是其组成的总和。锚点决定实体如何与用户的真实空间相关联。这一对(组件,锚点)就是设计单元。
-
渲染循环是连续的。时间是场景的一部分。每帧逻辑放入
System.update(context:);每次状态变更的逻辑放入RealityView.update:。混淆这两层(在SwiftUI的body中写每帧逻辑,在System.update中写状态驱动的逻辑)是最常见的架构错误。
完整的Apple生态系统系列:面向Apple Intelligence的类型化App Intents;用于跨LLM代理的MCP服务器;二者之间的路由问题;用于设备端LLM与Tool协议的Foundation Models;用于iOS锁屏状态机的Live Activities;Apple Watch上的watchOS运行时契约;作为框架基底的SwiftUI内部机制;用于视觉层的Liquid Glass模式;用于跨设备覆盖的多平台交付。中心枢纽位于Apple生态系统系列。如需更广泛的iOS与AI代理结合的背景,请参阅iOS Agent Development guide。
FAQ
RealityKit是visionOS上SwiftUI的替代品吗?
不是。RealityKit和SwiftUI是组合关系。SwiftUI处理2D窗口、控件和装饰;RealityKit处理锚定到真实世界参考点的3D场景。大多数非平凡的visionOS应用都同时使用两者,以RealityView作为桥梁,将RealityKit场景置入SwiftUI视图树中。
我应该何时使用RealityView而非Model3D?
使用Model3D显示单个静态3D资产(USDZ文件、单一产品渲染图)。使用RealityView随时间构建或变更3D场景(多个实体、物理、手势、手部追踪、锚定内容)。Model3D是廉价路径;RealityView是完整的ECS接口。
RealityKit中的Entity和Component有何区别?
实体是场景图中的节点。组件是附加到节点的类型化数据。ModelComponent赋予实体网格;InputTargetComponent使其可接受手势;CollisionComponent定义命中测试几何形状;PhysicsBodyComponent使其响应重力。您定义的自定义Component类型保存领域数据。行为是组合优于继承:实体的行为是其组件之和。
什么是锚点,为什么它们重要?
锚点将虚拟内容绑定到真实世界的参考点:用户的头、手、检测到的表面、识别出的图像、识别出的物体,或一个持久的世界点。锚点决定用户与实体的关系。放置在.plane目标(桌面)上的虚拟物体在用户绕行时保持原位;放置在.head目标上的虚拟物体则跟随用户的头部。选对锚点是空间功能的第一个设计决策。
RealityKit能在iOS上运行吗,而非仅限visionOS?
可以。RealityKit在iOS、iPadOS、macOS和visionOS上交付。基于ARKit的AR体验使用RealityKit的iOS接口。visionOS接口新增了空间专属的锚点类型(头、手、世界),iOS不暴露这些;但核心ECS模式是共享的。
References
-
作者在What SwiftUI Is Made Of中的分析,2026年4月30日,涵盖值类型视图树、结果构建器DSL和观察系统。 ↩
-
Apple Developer,“RealityKit”和“RealityKit Systems”。实体/组件/系统架构以及标准组件类型(ModelComponent、Transform、CollisionComponent、PhysicsBodyComponent、InputTargetComponent)。 ↩
-
Apple Developer,“AnchorEntity”、“AnchoringComponent”、“Scene content anchors”,以及ARKit的“Anchor”。RealityKit锚定目标(
.world、.head、.hand(_:location:)、.plane、.image、.referenceObject)以及在visionOS上提供底层数据的ARKit锚点结构体(WorldAnchor、HandAnchor、ImageAnchor、ObjectAnchor、PlaneAnchor)。 ↩ -
Apple Developer,“RealityKit Systems”以及WWDC 2024 session “Build a great visionOS app”。RealityKit基于帧驱动的模拟和渲染,以及
System.update(context:)每帧钩子。 ↩ -
Apple Developer,“RealityView”、“RealityViewAttachments”以及“BillboardComponent”。进入RealityKit的SwiftUI桥梁、
ViewAttachmentEntity检索模式,以及当2D附件应朝向佩戴者时的可选广告牌行为。 ↩↩ -
Apple Developer,“Adding 3D content to your app”和“InputTargetComponent”。空间场景中的手势分发;
InputTargetComponent和CollisionComponent作为输入选择启用对的角色。 ↩ -
Apple Developer,“Scene”以及基于Combine的
subscribe(to:on:_:)事件发布器,它使RealityKit侧的状态变化能通过在make:闭包中注册的回调反向传递到SwiftUI。 ↩ -
Apple Developer,“Model3D”。用于显示模型资产的SwiftUI视图;在动用完整RealityKit ECS接口之前的廉价路径。 ↩