name: sceneview-ios
description: Build 3D and AR apps on Apple platforms (iOS, macOS, visionOS) with SceneViewSwift — the SwiftUI wrapper around RealityKit. Use whenever the user asks for "3D in SwiftUI", "AR with ARKit in SwiftUI", a model viewer for iOS, or any Apple-platform 3D/AR app where the dependency is the SceneViewSwift Swift Package from github.com/sceneview/sceneview. For Jetpack Compose / Android use the sceneview skill instead; for the browser use sceneview-web. Skip for plain ARKit-SDK / SceneKit / Unity / Unreal / RealityKit-without-SceneViewSwift work.
license: Apache-2.0
metadata:
author: SceneView
source: https://github.com/sceneview/sceneview
last-updated: '2026-05-22'
keywords:
- sceneview
- sceneviewswift
- 3d
- ar
- arkit
- realitykit
- swiftui
- model viewer
- augmented reality
- ios
- macos
- visionos
- swift package manager
- usdz
What SceneViewSwift is
SceneViewSwift is the Apple-platform half of the SceneView SDK — a declarative
SwiftUI API over RealityKit. Same mental model as the Android library
(SceneView { } / ARSceneView { }) but expressed with SwiftUI result-builders
and view modifiers, not Jetpack Compose.
- 3D —
SceneView { … }SwiftUI view (iOS / macOS / visionOS). - AR —
ARSceneView(…)SwiftUI view (iOS only — ARKit). - Renderer — RealityKit. There is no Filament on Apple platforms.
- Distribution — Swift Package Manager. The package lives in the
github.com/sceneview/sceneviewmonorepo; consumers add it by URL and pin a version tag (currently4.18.0).
SceneViewSwift is consumable directly from Swift, and also underneath the
Flutter and React Native bridges.
Authoritative API reference
Three sources, in priority order:
docs/docs/cheatsheet-ios.mdin the repo — the complete Apple-platform API reference (every node factory, view modifier, environment preset, the Android↔Apple mapping table, and the iOS parity-status tables). https://github.com/sceneview/sceneview/blob/main/docs/docs/cheatsheet-ios.mdSceneViewSwift/Sources/SceneViewSwift/— the actual Swift source. When in doubt about a signature, read it. Node factories live underNodes/, the views inSceneView.swift/ARSceneView.swift.samples/ios-demo/SceneViewDemo/Views/Demos/— a working SwiftUI demo for every feature. Read the demo, do NOT improvise an API.
llms.txt at the repo root carries the cross-platform surface but is
Android-centric — prefer cheatsheet-ios.md for Swift.
When to use this skill
Trigger on any of:
- "Build me a 3D viewer / AR app in SwiftUI."
- "Load a
.usdz/.glb/.gltf/.realitymodel on iOS." - "Place a model on a detected AR plane in ARKit + SwiftUI."
- "Add 3D content to a visionOS / macOS app with SceneView."
- "Convert this SceneKit / RealityView code to SceneViewSwift."
Skip for plain ARKit-SDK, SceneKit, Unity, Unreal, or RealityKit projects that do NOT use the SceneViewSwift wrapper.
Setup
// Package.swift
.package(url: "https://github.com/sceneview/sceneview.git", from: "4.18.0")
import SceneViewSwift
For AR, the host app's Info.plist must declare NSCameraUsageDescription.
For ARRecorder saving to Photos, add NSPhotoLibraryAddUsageDescription.
The minimal correct 3D example
Verified against samples/ios-demo/.../ModelViewerDemo.swift and the
declarative @NodeBuilder initializer in SceneView.swift:
import SwiftUI
import SceneViewSwift
struct ModelViewerScreen: View {
@State private var model: ModelNode?
var body: some View {
SceneView { root in
if let model {
root.addChild(model.entity)
}
}
.environment(.studio) // IBL lighting preset
.cameraControls(.orbit) // .orbit | .pan | .firstPerson | native (iOS 18+): .none | .tilt | .dolly | .gimbal
.autoCenterContent(true)
.task {
model = try? await ModelNode.load("models/helmet.usdz")
model?.scaleToUnits(0.3)
model?.playAllAnimations()
}
}
}
The declarative form uses the @NodeBuilder result-builder — nodes are
expressions inside the trailing closure:
SceneView {
GeometryNode.cube(size: 0.3, color: .red)
.position(.init(x: -1, y: 0, z: -2))
GeometryNode.sphere(radius: 0.2, color: .blue)
.position(.init(x: 1, y: 0, z: -2))
}
.environment(.studio)
.cameraControls(.orbit)
The minimal correct AR example
Verified against samples/ios-demo/.../ARPlacementDemo.swift. ARSceneView is
a UIViewRepresentable — iOS only:
ARSceneView(
planeDetection: .horizontal, // .horizontal | .vertical | .both | .none
showPlaneOverlay: true,
showCoachingOverlay: true,
onTapOnPlane: { position, arView in
let anchor = AnchorNode.world(position: position)
let cube = GeometryNode.cube(size: 0.1, color: .blue)
anchor.add(cube.entity)
arView.scene.addAnchor(anchor.entity)
}
)
.onSessionStarted { arView in /* session began */ }
Critical rules (verified — do not break)
ModelNode.load(_:)isasync throws. It is NOT aremember*-style nullable. Call it inside a SwiftUI.task { }(or another async context) andtry/try?it. Store the result in@State.RealityKit entities are
@MainActor-isolated. Never mutate anEntityfromDispatchQueue.global(). Useawait MainActor.run { }to cross back. SwiftUI.taskalready runs on the main actor for view work.AnchorNodefactories are iOS-specific —AnchorNode.world(position:)andAnchorNode.plane(alignment:minimumBounds:). This differs from Android, whereAnchorNodewraps acom.google.ar.core.Anchor. Do NOT translate the AndroidAnchorNode(anchor:)shape to Swift.GeometryNodefactories are static methods:.cube(size:color:),.sphere(radius:color:),.cylinder(radius:height:color:),.plane(width:depth:color:),.cone(height:radius:color:),.torus(...),.capsule(...). There are NOCubeNode/SphereNodetypes — that naming is Android-only.LightNodefactories are.directional(color:intensity:castsShadow:),.point(color:intensity:attenuationRadius:),.spot(color:intensity:innerAngle:outerAngle:). Position/aim via the fluent.position(_:)/.lookAt(_:)modifiers — not Android'sLightManager.Typeenum +applylambda.Some Android APIs do not port to RealityKit. Before re-attacking a deprecated symbol, consult the "iOS parity status (#1036)" tables in
cheatsheet-ios.md:CameraNode.exposure,CameraNode.depthOfField, andLightNode.shadowColorare compile-warning no-ops on iOS;ARSceneView(playbackDataset:),StreetscapeGeometry, terrain/rooftop anchors have no ARKit equivalent.ARRecorderon iOS is record-only (ReplayKit screen capture) — there is no deterministic playback.SceneViewis cross-platform (iOS/macOS/visionOS);ARSceneViewis iOS only. macOS and visionOS get 3D but not the ARKit camera view.
Performance / hot paths
Don't drive SwiftUI @State from a per-frame loop (an onFrame / RealityKit
update closure). A @State write every frame churns the view body — the
per-frame loop should mutate entities (or a reference-box class you hold) and only
flip @State when UI-visible state actually changes. Same root rule as the other
platforms: never recompute or allocate per frame what you can read once and cache.
Full cross-platform guidance:
docs/docs/performance.md § Hot Paths & Allocation-Free APIs
(audit umbrella #2263).
Toolchain pairing
Pair this skill with Xcode's command-line tools:
xcrun simctl boot "iPhone 16"+xcodebuild -scheme … -destination …— build and run on the simulator.xcrun simctl io booted screenshot ui.png— capture the rendered scene.swift build/swift testfromSceneViewSwift/— build/test the package in isolation.
Resources
- Cheat sheet — pointer to the canonical
docs/docs/cheatsheet-ios.mdplus the most-used SwiftUI signatures. - Recipes — pointers to the working demo in
samples/ios-demo/for each canonical pattern. Read the demo, copy from it. - Migration — SceneKit / RealityView → SceneViewSwift, and the Android↔Apple mapping.
Workflow guidance
When the user asks for a SceneViewSwift feature:
- Confirm the Apple platform. iOS / macOS / visionOS —
ARSceneViewis iOS-only;SceneViewworks everywhere. - Pick the right entrypoint.
SceneView { }for 3D,ARSceneView(…)for AR. Mentionimport SceneViewSwiftand the SPM dependency line. - Read the matching demo under
samples/ios-demo/.../Demos/before writing code. Fall back tocheatsheet-ios.md. Never invent an API. ModelNode.loadis async — wrap it in.task { }, store in@State.- Keep entity mutation on
@MainActor. - For AR, remind the user about
NSCameraUsageDescriptioninInfo.plist. - If the user pastes Android Kotlin, do NOT translate it character by
character — the result-builder + modifiers + factory naming differ. Use the
Android↔Apple mapping table in
cheatsheet-ios.md. - Before reusing a deprecated symbol, check the iOS parity-status tables.