Creating a spatial drawing app with RealityKit
At a glance
| Item | Summary |
|---|---|
| Purpose | Use low-level mesh and texture APIs to achieve fast updates to a person’s brush strokes by integrating RealityKit with ARKit and SwiftUI. |
| App architecture | RealityKitDrawingApp owns window/space mode, DrawingDocument owns stroke state, AnchorEntityInputProvider supplies tracked input, and brush generators plus RealityKit systems update low-level meshes and textures. |
| Main patterns | SwiftUI scene composition, UI–RealityKit bridge, Observable state owner, Entity-component-system, Protocol capability boundary, Document-input-render pipeline |
| Project style | Code-rich sample with 46 scanned C/Objective-C header, Metal, Swift file(s) and 4242 implementation line(s). |
Project structure
Source bundle/
├── RealityKitDrawingApp/RealityKitDrawingApp.swift # RealityKitDrawingApp, Mode, SetModeKey
├── RealityKitDrawingApp/Document/DrawingDocument.swift # Chirality, InputData, DrawingDocument
├── RealityKitDrawingApp/Document/AnchorEntityInputProvider.swift # HandComponent, AnchorEntityInputProvider, DrawingSystem
├── RealityKitDrawingApp/Brushes/Solid/Style/SolidBrushStyleProvider.swift # SolidBrushStyleProvider, ThicknessType, Settings
├── RealityKitDrawingApp/Canvas/DrawingCanvasPlacementView.swift # DrawingCanvasPlacementView, DrawingCanvasPlacementComponent, DrawingCanvasPlacementSystem
├── RealityKitDrawingApp/Utilities/CurveProcessing.swift # HermiteInterpolant, HermiteControlPoint, SubdivisionSearchItem
├── RealityKitDrawingApp/Brushes/Solid/SolidBrushComponent.swift # SolidBrushComponent, SolidBrushSystem
└── RealityKitDrawingApp/Packages/RealityKitContent/Package.realitycomposerpro/ProjectData/main.json # authored RealityKit content
Structure observations
- The runtime boundary is WindowGroup + ImmersiveSpace; the tree is pruned to lifecycle, state, framework, rendering, and ECS files.
- The source is C/Objective-C header, Metal, Swift; role-named types and feature folders separate the major responsibilities.
- Authored
.reality,.rkassets, or Reality Composer Pro content is an implementation boundary; Swift loads or drives it rather than reproducing its entity graph.
Overall architecture
flowchart LR
RealityKitDrawingApp_1["RealityKitDrawingApp"]
WindowGroup___ImmersiveSpace_2["WindowGroup + ImmersiveSpace"]
DrawingCanvasPlacementView_3["DrawingCanvasPlacementView"]
DrawingDocument_4["DrawingDocument"]
AnchorEntityInputProvider_5["AnchorEntityInputProvider"]
RealityKit_entity_graph_6["RealityKit entity graph"]
tracked_input___low_level_RealityKit_drawing_7["tracked input + low-level RealityKit drawing"]
RealityKitDrawingApp_1 --> WindowGroup___ImmersiveSpace_2
WindowGroup___ImmersiveSpace_2 --> DrawingCanvasPlacementView_3
DrawingCanvasPlacementView_3 --> DrawingDocument_4
DrawingDocument_4 --> AnchorEntityInputProvider_5
AnchorEntityInputProvider_5 --> RealityKit_entity_graph_6
RealityKit_entity_graph_6 --> tracked_input___low_level_RealityKit_drawing_7
Reference code
RealityKitDrawingApp/RealityKitDrawingApp.swift:12 — executable or app-lifecycle anchor.
struct RealityKitDrawingApp: App {
// ...
}The diagram is a responsibility flow, not a claim that every adjacent node directly calls the next. It separates lifecycle, presentation, stable state, framework/session work, and the RealityKit entity graph.
Ownership and state
classDiagram
DrawingCanvasPlacementView *-- SpatialTrackingSession : session
DrawingDocument --> Entity : rootEntity
RealityKitDrawingApp *-- Mode : mode
RealityKitDrawingApp *-- BrushState : brushState
Ownership evidence
RealityKitDrawingApp/Canvas/DrawingCanvasPlacementView.swift:22 — representative stored state or nearest verified lifecycle anchor.
struct DrawingCanvasPlacementView: View {
// ...
@State private var session: SpatialTrackingSession?
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
DrawingCanvasPlacementView |
SpatialTrackingSession as session |
owns a SwiftUI-managed reference slot | The declaring type controls writes |
DrawingDocument |
Entity as rootEntity |
stores and coordinates | The declaring type controls writes |
RealityKitDrawingApp |
Mode as mode |
owns SwiftUI value state | The declaring type controls writes |
RealityKitDrawingApp |
BrushState as brushState |
creates and retains | The declaring type controls writes |
Ownership is intentionally narrow: environment, bindings, observed objects, weak links, and delegate callbacks do not prove lifetime ownership. Initialized stored services and wrapper-managed state do; entities added inside a RealityKit content closure belong to that entity graph.
Class and protocol design
| Type | Responsibility | Depends on or conforms to |
|---|---|---|
RealityKitDrawingApp (RealityKitDrawingApp/RealityKitDrawingApp.swift:12) |
Declares app lifecycle and top-level dependency lifetime. | App |
DrawingCanvasPlacementView (RealityKitDrawingApp/Canvas/DrawingCanvasPlacementView.swift:16) |
Presents UI and forwards gestures or lifecycle events. | View |
DrawingDocument (RealityKitDrawingApp/Document/DrawingDocument.swift:38) |
Owns feature state and domain transitions. | Concrete framework collaborators |
AnchorEntityInputProvider (RealityKitDrawingApp/Document/AnchorEntityInputProvider.swift:31) |
Wraps a framework, input, rendering, or session capability. | Concrete framework collaborators |
HermiteInterpolant (RealityKitDrawingApp/Utilities/CurveProcessing.swift:12) |
Declares a replaceable capability or collaboration contract. | Concrete framework collaborators |
SolidBrushComponent (RealityKitDrawingApp/Brushes/Solid/SolidBrushComponent.swift:10) |
Stores RealityKit entity data. | Component |
SolidBrushSystem (RealityKitDrawingApp/Brushes/Solid/SolidBrushComponent.swift:15) |
Updates entities matching a RealityKit component query. | System |
SparkleBrushComponent (RealityKitDrawingApp/Brushes/Sparkle/SparkleBrushComponent.swift:11) |
Stores RealityKit entity data. | Component |
Verified protocol-oriented seams: Float → HermiteInterpolant, SIMD2 → HermiteInterpolant, SIMD3 → HermiteInterpolant, SIMD4 → HermiteInterpolant. Framework conformances remain adapters, not proof of an app-wide protocol architecture.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
private(set) var samples: [CurveSample] = [] (RealityKitDrawingApp/Brushes/Solid/CurveExtruder.swift:30) |
private(set) |
The getter keeps its wider visibility, while writes stay in the declaring type and permitted same-file extensions. | Inference: Protect an invariant while allowing observation. |
private static let query = EntityQuery(where: .has(SolidBrushComponent.... (RealityKitDrawingApp/Brushes/Solid/SolidBrushComponent.swift:16) |
private |
Use stays inside the declaration and same-file extensions permitted by Swift. | Inference: Hide lifecycle-sensitive state or an implementation step. |
fileprivate var windowId: String { (RealityKitDrawingApp/RealityKitDrawingApp.swift:32) |
fileprivate |
Use is limited to declarations in this source file. | Inference: Share with same-file helpers without making the symbol module-wide. |
public let flatness: Float (RealityKitDrawingApp/Brushes/Solid/CurveProcessing/SmoothCurveSampler.swift:20) |
public |
The symbol is available to importing modules, subject to its containing type’s visibility. | Inference: Expose an intentional package or cross-target surface. |
No reviewed Swift access example uses open; declarations without a modifier are internal.
Objective-C/C header and implementation visibility is documented separately from Swift lexical access control.
Reference code
RealityKitDrawingApp/Brushes/Solid/CurveExtruder.swift:30 — representative visibility boundary.
private(set) var samples: [CurveSample] = []
/// The number of samples in `samples` that have been meshed in `lowLevelMesh`.
private var cachedSampleCount: Int = 0
/// The number of samples for which `lowLevelMesh` has capacity.
@MainActor
private var sampleCapacity: Int {
// ...
}Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application/command lifecycle | RealityKitDrawingApp |
The executable boundary determines app, controller, scene, or command lifetime. |
| Presentation and user intent | DrawingCanvasPlacementView |
The view/controller forwards input and owns only presentation-local work. |
| Shared feature state and commands | DrawingDocument |
A stable owner prevents view recomputation or callbacks from duplicating transitions. |
| Framework/session/render coordination | AnchorEntityInputProvider |
The role-named collaborator isolates long-lived or per-frame work from presentation code. |
| Per-entity data and repeated behavior | RealityKitDrawingApp/Brushes/Solid/SolidBrushComponent.swift:15 |
Components carry data; the system queries and updates matching entities. |
| GPU and low-level resource work | RealityKitDrawingApp/Splash Screen/SplashScreenBackgroundComponent.swift:7 |
Buffer, texture, shader, or frame-resource mutation stays in a rendering/compute boundary. |
| Authored scene composition | RealityKitDrawingApp/Packages/RealityKitContent/Package.realitycomposerpro/ProjectData/main.json |
Reality Composer Pro owns hierarchy, materials, animation, or behavior that Swift loads and coordinates. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| SwiftUI scene composition | RealityKitDrawingApp/RealityKitDrawingApp.swift:12 |
Keeps windows, volumes, and immersive presentation visible at the app boundary. |
| UI–RealityKit bridge | RealityKitDrawingApp/Canvas/DrawingCanvasPlacementView.swift:62 |
Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle. |
| Observable state owner | RealityKitDrawingApp/Document/DrawingDocument.swift:38 |
Keeps shared feature state in a stable owner rather than in transient view values. |
| Entity-component-system | RealityKitDrawingApp/Brushes/Solid/SolidBrushComponent.swift:15 |
Stores per-entity data in components and advances repeated behavior in registered systems. |
| Protocol capability boundary | RealityKitDrawingApp/Utilities/CurveProcessing.swift:23 |
Defines a local capability with concrete conformers; this is the verified protocol-oriented seam. |
| Document-input-render pipeline | RealityKitDrawingApp/Document/DrawingDocument.swift:38 |
Separates durable stroke state, tracked input, and fast low-level mesh/texture generation. |
Naming conventions
- Role suffixes are evidence, not decoration: App:
RealityKitDrawingApp; Document:DrawingDocument; Provider:AnchorEntityInputProvider,SolidBrushStyleProvider,SparkleBrushStyleProvider; System:DrawingCanvasPlacementSystem,DrawingCanvasVisualizationSystem,DrawingSystem,SolidBrushSystem,SparkleBrushSystem; Component:DrawingCanvasPlacementComponent,DrawingCanvasVisualizationComponent,HandComponent,SolidBrushComponent,SparkleBrushComponent. - ECS names pair data and behavior: components
SolidBrushComponent,SparkleBrushComponent,DrawingCanvasPlacementComponent,DrawingCanvasVisualizationComponent,HandComponent; systemsSolidBrushSystem,SparkleBrushSystem,DrawingCanvasPlacementSystem,DrawingCanvasVisualizationSystem,DrawingSystem. - Protocols:
HermiteInterpolant. - Commands use verb-led methods:
setMode,receive,update,radius,styleInput,distance,length,dot. - Files and folders use primary-type or responsibility names such as
Views,Models,Rendering,Components,Systems,Packages, or feature names where those boundaries exist.
Architecture takeaways
- Treat
RealityKitDrawingAppas the owner of executable or scene lifecycle, not automatically as the owner of every entity created later. - Keep view/controller input near presentation, but move sessions, reconstruction, capture, rendering, shared game state, or documents into stable owners when their lifetime exceeds one callback or render pass.
- Use components for per-entity data and systems for repeated simulation instead of centralizing every update in a view model or controller.
- Count authored Reality Composer Pro assets as implementation; the Swift shell is not the complete architecture.
Source map
| Source file | Relevant symbols |
|---|---|
RealityKitDrawingApp/RealityKitDrawingApp.swift:12 |
RealityKitDrawingApp, Mode, SetModeKey |
RealityKitDrawingApp/Document/DrawingDocument.swift:14 |
Chirality, InputData, DrawingDocument |
RealityKitDrawingApp/Document/AnchorEntityInputProvider.swift:11 |
HandComponent, AnchorEntityInputProvider, DrawingSystem |
RealityKitDrawingApp/Brushes/Solid/Style/SolidBrushStyleProvider.swift:16 |
SolidBrushStyleProvider, ThicknessType, Settings |
RealityKitDrawingApp/Canvas/DrawingCanvasPlacementView.swift:16 |
DrawingCanvasPlacementView, DrawingCanvasPlacementComponent, DrawingCanvasPlacementSystem |
RealityKitDrawingApp/Utilities/CurveProcessing.swift:12 |
HermiteInterpolant, HermiteControlPoint, SubdivisionSearchItem |
RealityKitDrawingApp/Brushes/Solid/SolidBrushComponent.swift:10 |
SolidBrushComponent, SolidBrushSystem |