$npx -y skills add dpearson2699/swift-ios-skills --skill realitykitBuild iOS augmented reality and 3D experiences with RealityKit and ARKit. Use when adding RealityView content, loading entities or USDZ models, anchoring objects to planes or world positions, distinguishing entity hit tests from ARKit real-world raycasts, handling AR camera avail
| 1 | # RealityKit |
| 2 | |
| 3 | Build AR experiences on iOS using RealityKit for rendering and ARKit for world |
| 4 | tracking. Covers `RealityView`, entity management, raycasting, scene |
| 5 | understanding, and gesture-based interactions. Targets Swift 6.3 / iOS 26+. |
| 6 | |
| 7 | ## Contents |
| 8 | |
| 9 | - [Setup](#setup) |
| 10 | - [RealityView Basics](#realityview-basics) |
| 11 | - [Loading and Creating Entities](#loading-and-creating-entities) |
| 12 | - [Anchoring and Placement](#anchoring-and-placement) |
| 13 | - [Raycasting](#raycasting) |
| 14 | - [Gestures and Interaction](#gestures-and-interaction) |
| 15 | - [Scene Understanding](#scene-understanding) |
| 16 | - [Common Mistakes](#common-mistakes) |
| 17 | - [Review Checklist](#review-checklist) |
| 18 | - [References](#references) |
| 19 | |
| 20 | ## Setup |
| 21 | |
| 22 | ### Project Configuration |
| 23 | |
| 24 | 1. Add `NSCameraUsageDescription` to Info.plist |
| 25 | 2. On iOS, `RealityViewCameraContent` displays an AR camera view by default (iOS 18+, macOS 15+); use `.virtual` camera mode for explicit non-AR fallback |
| 26 | 3. No entitlement is required for basic AR. If AR is core to the app, add the `arkit` required-device capability; otherwise gate AR UI with `isSupported`. |
| 27 | |
| 28 | ### Device Requirements |
| 29 | |
| 30 | Check the exact AR configuration's `isSupported` value before presenting AR UI. |
| 31 | |
| 32 | ```swift |
| 33 | import ARKit |
| 34 | |
| 35 | guard ARWorldTrackingConfiguration.isSupported else { |
| 36 | showUnsupportedDeviceMessage() |
| 37 | return |
| 38 | } |
| 39 | ``` |
| 40 | |
| 41 | ### Key Types |
| 42 | |
| 43 | | Type | Platform | Role | |
| 44 | |---|---|---| |
| 45 | | `RealityView` | iOS 18+, visionOS 1+ | SwiftUI view that hosts RealityKit content | |
| 46 | | `RealityViewCameraContent` | iOS 18+, macOS 15+ | Content displayed through an AR camera view on iOS, non-AR on macOS | |
| 47 | | `Entity` | All | Base class for all scene objects | |
| 48 | | `ModelEntity` | All | Entity with a visible 3D model | |
| 49 | | `AnchorEntity` | All | Tethers entities to a real-world anchor | |
| 50 | |
| 51 | ## RealityView Basics |
| 52 | |
| 53 | `RealityView` is the SwiftUI entry point for RealityKit. |
| 54 | `RealityViewCameraContent` is the iOS/macOS content type. On iOS, it uses an AR |
| 55 | camera view by default and can use `content.camera = .virtual` for non-AR mode |
| 56 | when requested or when AR/camera access is unavailable. |
| 57 | |
| 58 | ```swift |
| 59 | import ARKit |
| 60 | import SwiftUI |
| 61 | import RealityKit |
| 62 | |
| 63 | struct ARExperienceView: View { |
| 64 | var body: some View { |
| 65 | RealityView { (content: RealityViewCameraContent) in |
| 66 | if !ARWorldTrackingConfiguration.isSupported { |
| 67 | content.camera = .virtual |
| 68 | } |
| 69 | |
| 70 | let sphere = ModelEntity( |
| 71 | mesh: .generateSphere(radius: 0.05), |
| 72 | materials: [SimpleMaterial( |
| 73 | color: .blue, |
| 74 | isMetallic: true |
| 75 | )] |
| 76 | ) |
| 77 | sphere.position = [0, 0, -0.5] // 50cm in front of camera |
| 78 | content.add(sphere) |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | ``` |
| 83 | |
| 84 | ### Make and Update Pattern |
| 85 | |
| 86 | Use the `update` closure to respond to SwiftUI state changes: |
| 87 | |
| 88 | ```swift |
| 89 | struct PlacementView: View { |
| 90 | @State private var modelColor: UIColor = .red |
| 91 | |
| 92 | var body: some View { |
| 93 | RealityView { content in |
| 94 | let box = ModelEntity( |
| 95 | mesh: .generateBox(size: 0.1), |
| 96 | materials: [SimpleMaterial( |
| 97 | color: .red, |
| 98 | isMetallic: false |
| 99 | )] |
| 100 | ) |
| 101 | box.name = "colorBox" |
| 102 | box.position = [0, 0, -0.5] |
| 103 | content.add(box) |
| 104 | } update: { content in |
| 105 | if let box = content.entities.first( |
| 106 | where: { $0.name == "colorBox" } |
| 107 | ) as? ModelEntity { |
| 108 | box.model?.materials = [SimpleMaterial( |
| 109 | color: modelColor, |
| 110 | isMetallic: false |
| 111 | )] |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | Button("Change Color") { |
| 116 | modelColor = modelColor == .red ? .green : .red |
| 117 | } |
| 118 | } |
| 119 | } |
| 120 | ``` |
| 121 | |
| 122 | ## Loading and Creating Entities |
| 123 | |
| 124 | ### Loading from USDZ Files |
| 125 | |
| 126 | Load 3D models asynchronously to avoid blocking the main thread: |
| 127 | |
| 128 | ```swift |
| 129 | RealityView { content in |
| 130 | if let robot = try? await ModelEntity(named: "robot") { |
| 131 | robot.position = [0, -0.2, -0.8] |
| 132 | robot.scale = [0.01, 0.01, 0.01] |
| 133 | content.add(robot) |
| 134 | } |
| 135 | } |
| 136 | ``` |
| 137 | |
| 138 | ### Adding Components |
| 139 | |
| 140 | Entities use an ECS (Entity Component System) architecture. Add components |
| 141 | to give entities behavior: |
| 142 | |
| 143 | ```swift |
| 144 | let box = ModelEntity( |
| 145 | mesh: .generateBox(size: 0.1), |
| 146 | materials: [SimpleMaterial(color: .red, isMetallic: false)] |
| 147 | ) |
| 148 | |
| 149 | // Make it respond to physics |
| 150 | box.components.set(PhysicsBodyComponent( |
| 151 | massProperties: .default, |
| 152 | material: .default, |
| 153 | mode: .dynamic |
| 154 | )) |
| 155 | |
| 156 | // Add collision shape for interaction |
| 157 | box.components.set(CollisionComponent( |
| 158 | shapes: [.generateBox(size: [0.1 |