使用 RealityKit 和 ARKit 构建 iOS 增强现实 (AR) 与 3D 体验。适用于添加 RealityView 内容、加载实体 (Entity) 或 USDZ 模型、将对象锚定到平面或世界坐标、区分实体碰撞检测 (Hit Test) 与 ARKit 真实世界射线检测 (Raycast)、处理 AR 相机可用性、世界追踪、场景更新,以及 RealityKit 实体的手势与交互等场景。
RealityKit
在 iOS 上使用 RealityKit 进行渲染并结合 ARKit 实现世界追踪,构建地道的 AR 体验。涵盖 RealityView、实体管理、射线检测、场景理解以及基于手势的交互。适配 Swift 6.3 / iOS 26+。
目录
环境配置
项目配置
- 在
Info.plist中添加NSCameraUsageDescription权限说明 - 在 iOS 上,
RealityViewCameraContent默认会显示 AR 相机视图(iOS 18+、macOS 15+);若需明确降级至非 AR 模式,请将相机模式设为.virtual - 基础 AR 体验无需额外申请权限配置 (Entitlement)。如果 AR 是应用的核心功能,请在 Info.plist 的 required-device capability 中添加
arkit;否则请通过isSupported动态控制 AR 界面展示。
设备要求
在展示 AR UI 之前,务必针对具体的 AR 配置检查其 isSupported 属性。
import ARKit
guard ARWorldTrackingConfiguration.isSupported else {
showUnsupportedDeviceMessage()
return
}
核心类型
| 类型 | 支持平台 | 作用 |
|---|---|---|
RealityView |
iOS 18+, visionOS 1+ | 承载 RealityKit 内容的 SwiftUI 视图 |
RealityViewCameraContent |
iOS 18+, macOS 15+ | 在 iOS 上通过 AR 相机视图展示内容,在 macOS 上展示非 AR 内容 |
Entity |
全平台 | 所有场景对象的基类 |
ModelEntity |
全平台 | 带有可视 3D 模型的 Entity |
AnchorEntity |
全平台 | 将实体锚定到真实世界的指定锚点 |
RealityView 基础
RealityView 是 RealityKit 在 SwiftUI 中的入口。
RealityViewCameraContent 是 iOS/macOS 对应的内容类型。在 iOS 上,它默认使用 AR 相机视图;当手动设置或 AR/相机权限不可用时,可使用 content.camera = .virtual 切换至非 AR 模式。
import ARKit
import SwiftUI
import RealityKit
struct ARExperienceView: View {
var body: some View {
RealityView { (content: RealityViewCameraContent) in
if !ARWorldTrackingConfiguration.isSupported {
content.camera = .virtual
}
let sphere = ModelEntity(
mesh: .generateSphere(radius: 0.05),
materials: [SimpleMaterial(
color: .blue,
isMetallic: true
)]
)
sphere.position = [0, 0, -0.5] // 位于相机前方 50 厘米
content.add(sphere)
}
}
}
Make 与 Update 范式
使用 update 闭包来响应 SwiftUI 的状态更新:
struct PlacementView: View {
@State private var modelColor: UIColor = .red
var body: some View {
RealityView { content in
let box = ModelEntity(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(
color: .red,
isMetallic: false
)]
)
box.name = "colorBox"
box.position = [0, 0, -0.5]
content.add(box)
} update: { content in
if let box = content.entities.first(
where: { $0.name == "colorBox" }
) as? ModelEntity {
box.model?.materials = [SimpleMaterial(
color: modelColor,
isMetallic: false
)]
}
}
Button("切换颜色") {
modelColor = modelColor == .red ? .green : .red
}
}
}
加载与创建实体
从 USDZ 文件加载
建议异步加载 3D 模型,避免阻塞主线程:
RealityView { content in
if let robot = try? await ModelEntity(named: "robot") {
robot.position = [0, -0.2, -0.8]
robot.scale = [0.01, 0.01, 0.01]
content.add(robot)
}
}
添加组件 (Component)
实体基于 ECS(实体组件系统)架构设计。可通过挂载不同的组件来赋予实体特定的行为特征:
let box = ModelEntity(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .red, isMetallic: false)]
)
// 赋予物理特性,使其响应物理引擎
box.components.set(PhysicsBodyComponent(
massProperties: .default,
material: .default,
mode: .dynamic
))
// 添加碰撞形状以支持交互检测
box.components.set(CollisionComponent(
shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
))
// 开启输入响应,接收手势事件
box.components.set(InputTargetComponent())
锚定与放置
AnchorEntity
使用 AnchorEntity 将内容绑定到检测到的物理表面或特定的世界坐标:
RealityView { content in
// 锚定到水平面
let floorAnchor = AnchorEntity(.plane(
.horizontal,
classification: .floor,
minimumBounds: [0.2, 0.2]
))
let model = ModelEntity(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .orange, isMetallic: false)]
)
floorAnchor.addChild(model)
content.add(floorAnchor)
}
锚定目标类型
| 目标类型 | 说明 |
|---|---|
.plane(.horizontal, ...) |
水平面(如地面、桌面) |
.plane(.vertical, ...) |
垂直面(如墙面) |
.plane(.any, ...) |
任意检测到的平面 |
.world(transform:) |
固定的世界空间坐标点 |
射线检测
请务必区分 RealityKit 的场景内查询与 ARKit 真实世界的射线检测:
RealityViewCameraContent.ray(through:in:to:)在 RealityKit 坐标系中返回一条相机射线。它用于将屏幕点投射到虚拟场景中,并不代表检测到了真实物理表面。RealityViewCameraContent.hitTest(point:in:query:mask:)用于检测带有CollisionComponent碰撞形状的虚拟实体。请将碰撞形状用于实体选中和手势拖拽,而不是用于 ARKit 的平面检测。- 若只需在检测到的平面上进行简单放置,请直接使用
AnchorEntity(.plane(...))。 - 当需要与真实物理表面进行一次性相交检测时,请结合使用 ARKit 的
ARRaycastQuery和ARSession.raycast(_:),随后通过AnchorEntity(raycastResult:)挂载实体。
let results = session.raycast(query)
if let result = results.first {
let anchor = AnchorEntity(raycastResult: result)
anchor.addChild(model)
content.add(anchor)
}
切勿将实体的 Hit Test 混淆或替代为 ARKit 的物理表面 Raycast。
手势与交互
若要实现手势交互,实体必须同时添加用于检测碰撞的 CollisionComponent 和用于响应输入的 InputTargetComponent。
实体拖拽手势
struct DraggableARView: View {
var body: some View {
RealityView { content in
let box = ModelEntity(
mesh: .generateBox(size: 0.1),
materials: [SimpleMaterial(color: .blue, isMetallic: true)]
)
box.position = [0, 0, -0.5]
box.components.set(CollisionComponent(
shapes: [.generateBox(size: [0.1, 0.1, 0.1])]
))
box.components.set(InputTargetComponent())
box.name = "draggable"
content.add(box)
}
.gesture(
DragGesture()
.targetedToAnyEntity()
.onChanged { value in
let entity = value.entity
guard let parent = entity.parent else { return }
entity.position = value.convert(
value.location3D,
from: .local,
to: parent
)
}
)
}
}
场景理解
逐帧更新
连续的场景更新工作应当订阅 SceneEvents.Update,而非使用 SwiftUI 的定时器来驱动 RealityKit。保持订阅有效并使用 event.deltaTime;详情参阅 实体动画。
平台架构边界
在 visionOS 上,ARKit 提供了包含 ARKitSession、WorldTrackingProvider 及 PlaneDetectionProvider 在内的全新 API 体系。这些 visionOS 专属类型在 iOS 上不可用。在 iOS 端,RealityKit 会自动通过 RealityViewCameraContent 处理世界追踪。
针对 iOS 的架构设计或迁移,请先通过 ARWorldTrackingConfiguration.isSupported 检查 AR 可用性,使用 RealityViewCameraContent 承载内容,并利用 AnchorEntity 放置基于 Entity/ModelEntity 构建的场景。
职责划分: CollisionComponent + InputTargetComponent 负责 RealityKit 内部的交互;AccessibilityComponent 负责实体的无障碍元数据;而更具体的 SwiftUI 手势逻辑以及 VoiceOver / 切换控制 (Switch Control) 策略则应交给上层组件处理。
若项目中存在现有的 SCNView/SCNNode 代码,请将其作为独立的 SceneKit 分支运行或彻底迁移至 RealityKit,切勿在同一个场景树中混合使用。
常见误区
忌:跳过 AR 设备能力检查
在展示 AR 界面前,请务必先执行 环境配置 章节中的配置支持度检查。若设备不支持,应降级显示非 AR 内容或展示明确的不可用提示。
忌:同步加载大型模型
在主线程上同步加载大型 USDZ 文件会导致界面丢帧甚至卡死。RealityView 的 make 闭包本身支持 async —— 请务必使用异步加载。
// ❌ 错误做法 —— 同步加载阻塞主线程
RealityView { content in
let model = try! Entity.load(named: "large-scene")
content.add(model)
}
// ✅ 正确做法 —— 异步加载
RealityView { content in
if let model = try? await ModelEntity(named: "large-scene") {
content.add(model)
}
}
忌:可交互实体漏加碰撞与输入组件
支持交互的实体必须同时具备 手势与交互 中提到的两个组件;如果缺失,点击和拖拽手势将会直接穿透该实体。
忌:在 update 闭包中创建新实体
每次 SwiftUI 状态发生变化时,update 闭包都会被重新调用。在 update 中创建实体会导致每次渲染重新叠加重复的对象。
// ❌ 错误做法 —— 每次状态更新都会重复创建并添加实体
RealityView { content in
// 保持为空
} update: { content in
let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
content.add(sphere) // 每次 update 都会反复 add
}
// ✅ 正确做法 —— 在 make 中创建,在 update 中修改
RealityView { content in
let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
sphere.name = "mySphere"
content.add(sphere)
} update: { content in
if let sphere = content.entities.first(
where: { $0.name == "mySphere" }
) as? ModelEntity {
// 仅修改已有实体的属性
sphere.position.y = newYPosition
}
}
忌:忽视相机权限处理
iOS 上的 RealityKit 依赖相机访问权限。若用户拒绝授权,视图将直接黑屏且没有任何原因提示。
// ❌ 错误做法 —— 未处理权限逻辑
RealityView { content in
// 用户拒绝相机权限时会直接黑屏
}
// ✅ 正确做法 —— 先校验并请求权限
struct ARContainerView: View {
@State private var cameraAuthorized = false
var body: some View {
Group {
if cameraAuthorized {
RealityView { content in
// AR 内容
}
} else {
ContentUnavailableView(
"需要相机权限",
systemImage: "camera.fill",
description: Text("请在“设置”中开启相机权限以使用 AR 功能。")
)
}
}




