Sample CodevisionOSReviewed 2026-07-21View on Apple Developer

Manipulating models with RealityKit

At a glance

Item Summary
Purpose Interact with detailed 3D models using manipulation and clipping controls.
App architecture ModelManipulatorApp owns AppModel for loaded entities and selection; views forward gestures while ClippingController and a sync component/system isolate clipping-volume behavior from UI state.
Main patterns SwiftUI scene composition, UI–RealityKit bridge, Observable state owner, Entity-component-system, Async update consumer, Clipping-state adapter
Project style Code-rich sample with 27 scanned Swift file(s) and 1786 implementation line(s).

Project structure

Source bundle/
└── ModelManipulator/
    ├── Models/
    │   ├── AppModel.swift  # AppModel, ImmersiveSpaceState, ViewState
    │   └── ClippingController.swift  # ClippingController
    ├── Systems/
    │   └── ClippingTransformSyncSystem.swift  # ClippingTransformSyncSystem
    ├── ModelManipulatorApp.swift  # ModelManipulatorApp
    ├── Views/
    │   └── ContentView.swift  # ContentView
    └── Components/
        ├── ClippingBoundsCacheComponent.swift  # ClippingBoundsCacheComponent, ClippingState
        └── ClippingTransformSyncComponent.swift  # ClippingTransformSyncComponent

Structure observations

  • The runtime boundary is WindowGroup + ImmersiveSpace; the tree is pruned to lifecycle, state, framework, rendering, and ECS files.
  • The source is Swift; role-named types and feature folders separate the major responsibilities.

Overall architecture

Reference code

ModelManipulator/ModelManipulatorApp.swift:11 — executable or app-lifecycle anchor.

struct ModelManipulatorApp: 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

Ownership evidence

ModelManipulator/ModelManipulatorApp.swift:12 — representative stored state or nearest verified lifecycle anchor.

    @State private var appModel = AppModel()

    @Environment(\.dismissImmersiveSpace) private var dismissImmersiveSpace
    @Environment(\.dismissWindow) private var dismissWindow
    @Environment(\.openImmersiveSpace) private var openImmersiveSpace
    @Environment(\.openWindow) private var openWindow
    @Environment(\.scenePhase) private var scenePhase
Owner Object or state Relationship Mutation authority
ModelManipulatorApp AppModel as appModel creates and retains The declaring type controls writes
AppModel Entity as modelEntity stores and coordinates The owning type coordinates writes
ContentView AppModel as appModel receives a shared or non-owning reference The upstream owner controls lifetime; this scope may invoke the exposed mutable API
ModelManipulatorApp ScenePhase as scenePhase receives a shared or non-owning reference The upstream owner controls lifetime; this scope may invoke the exposed mutable API

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
ModelManipulatorApp (ModelManipulator/ModelManipulatorApp.swift:11) Declares app lifecycle and top-level dependency lifetime. App
ContentView (ModelManipulator/Views/ContentView.swift:10) Presents UI and forwards gestures or lifecycle events. View
AppModel (ModelManipulator/Models/AppModel.swift:15) Owns feature state and domain transitions. Concrete framework collaborators
ClippingController (ModelManipulator/Models/ClippingController.swift:11) Coordinates long-lived framework or cross-view work. Concrete framework collaborators
ClippingBoundsCacheComponent (ModelManipulator/Components/ClippingBoundsCacheComponent.swift:10) Stores RealityKit entity data. Component
ClippingTransformSyncComponent (ModelManipulator/Components/ClippingTransformSyncComponent.swift:10) Stores RealityKit entity data. Component
ClippingTransformSyncSystem (ModelManipulator/Systems/ClippingTransformSyncSystem.swift:12) Updates entities matching a RealityKit component query. System
EntityHierarchyView (ModelManipulator/Views/EntityHierarchyView.swift:11) Presents UI and forwards gestures or lifecycle events. View

The reviewed source defines no local substitution protocol. Its App, View, delegate, renderer, ARKit, and RealityKit conformances are framework-facing contracts, so this document does not label the whole app protocol-oriented.

Access control

Symbol Access Verified effect Likely rationale
private(set) var isModelExpanded = false (ModelManipulator/Models/AppModel.swift:28) 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 weak var clippedEntity: Entity? (ModelManipulator/Entities/ClippingControlEntity.swift:13) private Use stays inside the declaration and same-file extensions permitted by Swift. Inference: Hide lifecycle-sensitive state or an implementation step.

No reviewed Swift access example uses fileprivate, public, open; declarations without a modifier are internal.

Reference code

ModelManipulator/Models/AppModel.swift:28 — representative visibility boundary.

    private(set) var isModelExpanded = false
    /// A Boolean value that indicates whether the model supports expansion into component parts.
    var isModelExpandable: Bool { modelEntity?.assemblyParent != nil }

Logic ownership and placement

Logic Owning type or file Placement rationale
Application/command lifecycle ModelManipulatorApp The executable boundary determines app, controller, scene, or command lifetime.
Presentation and user intent ContentView The view/controller forwards input and owns only presentation-local work.
Shared feature state and commands AppModel A stable owner prevents view recomputation or callbacks from duplicating transitions.
Framework/session/render coordination ClippingController The role-named collaborator isolates long-lived or per-frame work from presentation code.
Per-entity data and repeated behavior ModelManipulator/Systems/ClippingTransformSyncSystem.swift:12 Components carry data; the system queries and updates matching entities.

Design patterns

Pattern Source evidence Purpose or tradeoff
SwiftUI scene composition ModelManipulator/ModelManipulatorApp.swift:11 Keeps windows, volumes, and immersive presentation visible at the app boundary.
UI–RealityKit bridge ModelManipulator/Views/ImmersiveView.swift:21 Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle.
Observable state owner ModelManipulator/Models/AppModel.swift:15 Keeps shared feature state in a stable owner rather than in transient view values.
Entity-component-system ModelManipulator/Systems/ClippingTransformSyncSystem.swift:12 Stores per-entity data in components and advances repeated behavior in registered systems.
Async update consumer ModelManipulator/Models/AppModel.swift:136 Consumes long-lived framework output in a cancellable asynchronous task.
Clipping-state adapter ModelManipulator/Models/AppModel.swift:43 Moves clipping-resource details out of SwiftUI and synchronizes per-entity transform state through ECS.

Naming conventions

  • Role suffixes are evidence, not decoration: App: ModelManipulatorApp; Model: AppModel; Controller: ClippingController; System: ClippingTransformSyncSystem; Component: ClippingBoundsCacheComponent, ClippingTransformSyncComponent.
  • ECS names pair data and behavior: components ClippingBoundsCacheComponent, ClippingTransformSyncComponent; systems ClippingTransformSyncSystem.
  • Protocols: no app-defined protocol in the reviewed source.
  • Commands use verb-led methods: handleClippingStateChange, presentFileImporter, reset, selectDefaultModel, selectModel, setupClippingObservation, toggleModelExpansion, toggleModelLock.
  • 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 ModelManipulatorApp as 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.
  • Tie asynchronous session/output consumers to an explicit owner so cancellation and teardown follow the same lifecycle.

Source map

Source file Relevant symbols
ModelManipulator/Models/AppModel.swift:15 AppModel, ImmersiveSpaceState, ViewState
ModelManipulator/Models/ClippingController.swift:11 ClippingController
ModelManipulator/Systems/ClippingTransformSyncSystem.swift:12 ClippingTransformSyncSystem
ModelManipulator/ModelManipulatorApp.swift:11 ModelManipulatorApp
ModelManipulator/Views/ContentView.swift:10 ContentView
ModelManipulator/Components/ClippingBoundsCacheComponent.swift:10 ClippingBoundsCacheComponent, ClippingState
ModelManipulator/Components/ClippingTransformSyncComponent.swift:10 ClippingTransformSyncComponent