OPENAI-PLUGIN
OpenAI's unit of distribution is the **plugin**: a folder with a manifest at `.codex-plugin/plugin.json`, optional skills, an…
The **canonical, complete** API reference is [`docs/docs/cheatsheet-ios.md`](https://github.com/sceneview/sceneview/blob/main/docs/docs/cheatsheet-ios.md) in the repo — it carries every node factory, every view modifier, the environment presets, the Android↔Apple mapping table,
How it fires
How this agent gets triggered: by you, by Claude, or both.
Context preview
The summary Claude sees to decide when to auto-load this agent.
The **canonical, complete** API reference is [`docs/docs/cheatsheet-ios.md`](https://github.com/sceneview/sceneview/blob/main/docs/docs/cheatsheet-ios.md) in the repo — it carries every node factory, every view modifier, the environment presets, the Android↔Apple mapping table,
The **canonical, complete** API reference is [`docs/docs/cheatsheet-ios.md`](https://github.com/sceneview/sceneview/blob/main/docs/docs/cheatsheet-ios.md) in the repo — it carries every node factory, every view modifier, the environment presets, the Android↔Apple mapping table, and the iOS parity-status tables. **Read that file first.** This page is a quick extract of the most-used signatures, verified against `SceneViewSwift/Sources/SceneViewSwift/`.
| View | Platform | Demo | | --- | --- | --- | | `SceneView { … }` | iOS / macOS / visionOS | `ModelViewerDemo.swift` | | `ARSceneView(…)` | iOS only (ARKit) | `ARPlacementDemo.swift` |
// Declarative — @NodeBuilder result-builder
SceneView {
GeometryNode.cube(size: 0.3, color: .red)
}
// Imperative — gives you the RealityKit root Entity
SceneView { root in
root.addChild(model.entity)
}SceneView { … }
.environment(.studio) // IBL preset — see "Environment presets" below
.cameraControls(.orbit) // .orbit (default) | .pan | .firstPerson | native (iOS 18+): .none | .tilt | .dolly
.onEntityTapped { entity in }
.autoRotate(speed: 0.3) // turntable
.autoCenterContent(true) // translate content centroid to orbit pivot
.framingMargin(0.95) // auto-fit padding; 1.15 default, 1.0 = bounding sphere tangent, < 1 tighter
.contentInsets(EdgeInsets(top: 0, leading: 0, bottom: sheetHeight, trailing: 0)) // v4.53.0+, 3D SceneView only (not AR) — keep the subject clear of your sheet; view not resized, pose untouched when autoCenterContent(false)
.cameraOrbit(azimuth: .pi / 5, elevation: .pi / 15) // INITIAL orbit pose, radians (defaults 0, 30°)
.cameraPose(pose) // v4.27.0+ — continuous camera drive (SceneCameraPose, radians)
.onCameraChanged { new in Task { @MainActor in pose = new } } // v4.27.0+ — read-back after every change; the hop is required
.cameraGesturesEnabled(false) // v4.27.0+ — freeze drag/pinch without handing the camera to Apple
.onEntityTapHit { hit in } // v4.27.0+ — tap + world position (bounds centre, not the surface point)
.mainLight(.systemDefault) // see LightSlot
.fillLight(.systemDefault)
.renderQuality(.default) // .cinematic | .default | .performance
.contentID(model == nil ? nil : selectedID) // re-runs the content closure IN PLACE when the id changes**Swapping the model: `.contentID(_:)`, never SwiftUI's `.id(_:)`.** The content closure runs once, when the scene is created. Change `.contentID(_:)` to show a different model, and do not wrap the `SceneView` in an `if let model` that unmounts it while the next one loads — put the spinner in an overlay. Both change the view's *identity*, so SwiftUI discards the `RealityView` and builds a new one, and a re-created `RealityView` on iOS 26 Simulator intermittently renders nothing at all — no model, no skybox — permanently (#3008). The id must change every time the closure would build something different, **including "loading" → "loaded"**, so make it an `Optional`: keyed on the selection alone it already sits at its final value while the model is still being fetched and the scene stays empty forever.
ZStack {
SceneView { root in
guard let model else { return }
root.addChild(model.entity)
}
.contentID(model == nil ? nil : selectedID)
if model == nil { ProgressView() }
}Android needs no equivalent — its DSL content is re-read on recomposition.
The auto-fit fits the content's **bounding sphere** to the narrower frustum axis, then scales that distance by `framingMargin` — lower it to fill a tall portrait frame, but stay at or above ~`0.95` on an `autoRotate` scene or a long model clips at its broadside azimuth. `framingMargin` runs *inside* the auto-fit, so it is a **silent no-op under `.autoCenterContent(false)`**. Android's equivalent is `frameToContent(padding =)`, a fraction rather than a multiplier: `margin == 1 + padding` (iOS `1.15` = Android `0.15`). `cameraOrbit` seeds only the starting pose. Watch elevation: at the 60° vertical FOV the **30° default pitch puts the horizon on the top edge**, so a `showSkybox` environment shows no sky until you lower it.
ARSceneView(
planeDetection: .horizontal, // .horizontal | .vertical | .both | .none
showPlaneOverlay: true,
showCoachingOverlay: true,
showPlacementReticle: true, // continuous smoothed placement cursor (#894, opt-in)
groundingShadows: true, // contact shadows on tap-placed models (#894, DEFAULT ON — opt out with false; sync placement flow only)
onTapOnPlane: { position, arView in /* place content */ }
)
.onSessionStarted { arView in }
.cameraExposure(0.0) // EV stops — AR-only camera exposure (iOS-specific semantics)
.onFrame { frame, arView in } // (ARFrame, ARView)
.mainLight(.systemDefault)
.fillLight(.systemDefault)| Node | Factory | Notes | | --- | --- | --- | | `ModelNode` | `ModelNode.load("file.usdz")` | `async throws`. Then `.scaleToUnits(_:)`, `.centerOrigin(normalized:)` (apply before positioning), `.playAllAnimations()` | | `GeometryNode` | `.cube(size:color:)` `.sphere(radius:color:)` `.cylinder(radius:height:color:)` `.plane(width:depth:color:)` `.cone(height:radius:color:)` `.torus(...)` `.capsule(...)` | also `material: .pbr(...)` overloads; `unlit: Bool` for flat fill | | `LightNode` | `.directional(color:intensity:castsShadow:)` `.point(color:intensity:attenuationRadius:)` `.spot(color:intensity:innerAngle:outerAngle:)` | aim via `.position(_:)` / `.lookAt(_:)` | | `TextNode` | `TextNode(text:fontSize:color:depth:)` | `.centered()` | | `ImageNode` | `ImageNode.load("img.png")` | `async throws`; `width:` / `height:` | | `BillboardNode` | `BillboardNode(chil
The AI-first 3D & AR SDK for Jetpack Compose, SwiftUI and the web. Declarative 3D and AR for app developers who want a model on screen, or in the room, without a game engine: Filament + ARCore on Android, RealityKit + ARKit on Apple, Filament.js + WebXR in
OpenAI's unit of distribution is the **plugin**: a folder with a manifest at `.codex-plugin/plugin.json`, optional skills, an…
This directory ships the SceneView SDK as **agent skills** for AI coding assistants. Each skill is a self-contained `SKILL.md`…
This page covers the rewrites AI agents most often need when bringing existing Apple-platform 3D code onto SceneViewSwift. The…
**Do not improvise.** Every pattern below has a matching SwiftUI demo file in `samples/ios-demo/SceneViewDemo/Views/Demos/`. Read…