使用 RealityKit 與 ARKit 打造 iOS 擴增實境 (AR) 與 3D 體驗。適用於新增 RealityView 內容、載入 Entity 或 USDZ 模型、將物件錨定至平面或世界座標位置、區分 Entity 點擊測試 (hit test) 與 ARKit 真實世界光線投射 (raycast)、處理 AR 相機可用性、世界追蹤、場景更新,以及 RealityKit Entity 手勢與互動。
RealityKit
使用 RealityKit 處理算圖(rendering)並搭配 ARKit 進行世界追蹤,在 iOS 上打造 AR 體驗。涵蓋 RealityView、Entity 管理、光線投射(raycasting)、場景理解(scene understanding)以及手勢互動。適用於 Swift 6.3 / iOS 26+。
目錄
環境設定
專案設定
- 在 Info.plist 中新增
NSCameraUsageDescription - 在 iOS 上,
RealityViewCameraContent預設會顯示 AR 相機視圖(iOS 18+、macOS 15+);若需要明確的非 AR 備用方案(fallback),請使用.virtual相機模式 - 基礎 AR 無需特殊 Entitlement。若 AR 為 App 的核心功能,請新增
arkit必要的裝置功能要求(required-device capability);否則請使用isSupported控管 AR UI 的顯示。
裝置需求
在呈現 AR UI 之前,請先檢查具體 AR 設定(configuration)的 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 |
所有平台 | 所有場景物件的基底類別 (Base class) |
ModelEntity |
所有平台 | 帶有可見 3D 模型的 Entity |
AnchorEntity |
所有平台 | 將 Entity 繫結至真實世界的錨點 |
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] // 相機前方 50cm
content.add(sphere)
}
}
}
Make 與 Update 模式
使用 update 閉包(closure)來回應 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("Change Color") {
modelColor = modelColor == .red ? .green : .red
}
}
}
載入與建立 Entity
從 USDZ 檔案載入
非同步載入 3D 模型,避免阻礙主執行緒(main thread):
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
Entity 採用 ECS(Entity Component System,實體組件系統)架構。透過新增 Component 來賦予 Entity 行為:
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)
}
錨定目標 (Anchor Targets)
| 目標 | 說明 |
|---|---|
.plane(.horizontal, ...) |
水平平面(地板、桌面) |
.plane(.vertical, ...) |
垂直平面(牆面) |
.plane(.any, ...) |
任何偵測到的平面 |
.world(transform:) |
固定的世界空間位置 |
光線投射 (Raycasting)
請明確區分 RealityKit 場景查詢與 ARKit 的真實世界光線投射(raycast):
RealityViewCameraContent.ray(through:in:to:)會在 RealityKit 座標空間中傳回一條相機光線。它將螢幕點投影至虛擬場景中;這並不代表偵測到了真實物理表面。RealityViewCameraContent.hitTest(point:in:query:mask:)用於命中已透過CollisionComponent形狀設為可命中的虛擬 Entity。請將這些形狀用於 Entity 選取與目標手勢,而非 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)
}
請勿將 Entity 的點擊測試(hit test)混淆或替代 ARKit 的表面光線投射。
手勢與互動
若要實現基於手勢的 Entity 互動,請新增 CollisionComponent 以定義可點擊範圍形狀,並新增 InputTargetComponent 以支援輸入目標指定。
Entity 拖拽手勢
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
)
}
)
}
}
場景理解
每幀更新 (Per-Frame Updates)
若需要持續處理場景工作,請訂閱 SceneEvents.Update,而非使用 SwiftUI Timer 來驅動 RealityKit。請維持訂閱運作並善用 event.deltaTime;詳情請參考 Entity Animations。
平台邊界
在 visionOS 上,ARKit 提供了不同的 API 架構,包含 ARKitSession、WorldTrackingProvider 與 PlaneDetectionProvider。這些 visionOS 專用的型態在 iOS 上並不可用。在 iOS 上,RealityKit 會透過 RealityViewCameraContent 自動處理世界追蹤。
關於 iOS 架構或轉移(migration)注意事項:請使用 ARWorldTrackingConfiguration.isSupported 控制 AR 開關、透過 RealityViewCameraContent 承載內容,並使用以 AnchorEntity 放置的 Entity/ModelEntity 來建立場景。
職責劃分(Handoffs): CollisionComponent + InputTargetComponent 負責處理 RealityKit 的互動;AccessibilityComponent 負責處理 Entity 的無障礙元資料(metadata);而詳細的 SwiftUI 手勢與 VoiceOver/切換控制(Switch Control)策略則歸屬於相鄰元件。
請將現有的 SCNView/SCNNode 程式碼視為獨立的 SceneKit 路徑或明確轉移至 RealityKit 的項目,切勿建立混合的場景圖(scene graph)。
常見錯誤
錯誤作法:忽略 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)
}
}
錯誤作法:互動式 Entity 漏掉碰撞與輸入目標 Component
具備互動功能的 Entity 必須同時具備手勢與互動章節中提到的兩個 Component;若缺少它們,點擊與拖拽手勢將會直接穿透。
錯誤作法:在 update 閉包中建立新的 Entity
每當 SwiftUI 狀態發生變更時,update 閉包都會執行一次。若在其中建立 Entity,將導致每次算圖時重複產生相同的內容。
// 錯誤——每次狀態變更時都會重複建立 Entity
RealityView { content in
// 空白
} update: { content in
let sphere = ModelEntity(mesh: .generateSphere(radius: 0.05))
content.add(sphere) // 每次更新都會再次新增
}
// 正確——在 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 {
// 修改既有的 Entity
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(
"Camera Access Required",
systemImage: "camera.fill",
description: Text("Enable camera in Settings to use AR.")
)
}
}
<!-- truncated for translation batch; full body continues in source -->




