Canyon Crosser: Building a volumetric hike-planning app
At a glance
| Item | Summary |
|---|---|
| Purpose | Create a hike planning app using SwiftUI and RealityKit. |
| App architecture | A Swift sample with the source-visible chain CanyonCrosserApp → ContentView → AppModel → ClippingMarginPercentageSystem → SwiftUI / RealityKit APIs. |
| Main patterns | Protocol-oriented abstraction, Binding-based state propagation |
| Project style | 85 scanned source file(s) across Swift, organized around ranked entry, type, and file boundaries. |
| Execution model | Source-visible boundaries: @MainActor, async declaration or closure, Sendable or @Sendable, Task; none alone proves a background thread. |
| State/event model | Source-visible mechanisms: SwiftUI state property wrapper, @Observable. |
| Key frameworks/packages | SwiftUI, RealityKit, Foundation, RealityKitContent, Observation; these are source dependencies, not architecture labels. |
Project structure
Source bundle/
├── CanyonCrosser/
│ ├── CanyonCrosserApp.swift
│ ├── Views/
│ │ ├── Progress Slider/
│ │ │ └── WeatherOrTimeView.swift
│ │ └── Carousel/
│ │ └── CarouselLabelView.swift
│ ├── Models/
│ │ ├── AppModel/
│ │ │ └── AppModel.swift
│ │ ├── AppPhaseModel.swift
│ │ └── CarouselModel.swift
│ └── ECS/
│ ├── ClippingMarginPercentageComponent.swift
│ └── FeatheringSystem.swift
└── Packages/
└── RealityKitContent/
└── Sources/
└── RealityKitContent/
├── TimeOfDayLightComponent.swift
├── TimeOfDayLightSystem.swift
├── TimeOfDayMaterialComponent.swift
└── TimeOfDayMaterialSystem.swift
Structure observations
- Architecturally prominent files are ranked from entry points and role-named declarations; resource-only paths are omitted.
- Primary languages: Swift.
- The verified tree contains 5 project/configuration file(s) and 90 source declaration(s).
Overall architecture
flowchart LR
N1["CanyonCrosserApp"]
N2["ContentView"]
N3["AppModel"]
N4["ClippingMarginPercentageSystem"]
N5["SwiftUI / RealityKit APIs"]
N1 --> N2
N2 --> N3
N3 --> N4
N4 --> N5
Reference code
CanyonCrosser/CanyonCrosserApp.swift:11 — architecture anchor
@main
struct CanyonCrosserApp: App {
@State private var appModel: AppModel = AppModel()
@State private var appPhaseModel: AppPhaseModel = AppPhaseModel()
init() {
ClippingMarginPercentageSystem.registerSystem()
FeatheringSystem.registerSystem()
HikeSystem.registerSystem()
LightRotationSystem.registerSystem()
TimeOfDayLightSystem.registerSystem()
TimeOfDayMaterialSystem.registerSystem()
TimeOfDaySystem.registerSystem()
}
var body: some Scene {
WindowGroup() {
ContentView()
.environment(appModel)
.environment(appPhaseModel)
}
.defaultSize(width: 2.0 * 0.74, height: 2.0 * 0.74, depth: 2.0, in: .meters)
.windowResizability(.contentMinSize)
.windowStyle(.volumetric)
}
}Interpretation
The arrows summarize the source-visible entry, role-named types or folders, and framework direction; when nodes come from structural folders, the sequence is a high-level interpretation rather than proof that every adjacent node calls the next. Ownership is claimed only where the next section cites a stored property or assignment. The diagram is intentionally limited to the dominant path into visionOS.
Ownership and state
classDiagram
CanyonCrosserApp *-- AppModel : appModel
CanyonCrosserApp *-- AppPhaseModel : appPhaseModel
AppModel o-- ClippingMarginPercentageComponent : clippingMarginEnvironment
AppModel *-- Entity : root
Ownership evidence
CanyonCrosser/CanyonCrosserApp.swift:13 — stored dependency or nearest verified ownership anchor
@main
struct CanyonCrosserApp: App {
@State private var appModel: AppModel = AppModel()
// ...
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
CanyonCrosserApp |
AppModel (appModel) |
owns wrapper-managed state | Owning lexical scope |
CanyonCrosserApp |
AppPhaseModel (appPhaseModel) |
owns wrapper-managed state | Owning lexical scope |
AppModel |
ClippingMarginPercentageComponent (clippingMarginEnvironment) |
stores or receives | App/module collaborators |
AppModel |
Entity (root) |
creates and retains | App/module collaborators |
Composition arrows indicate a source-visible construction expression or locally owned value state; aggregation means the owner stores or receives a dependency without proving exclusive lifetime ownership.
Concurrency, scheduling, and thread safety
Evidence limit: actor isolation, async/await, or Task creation does not by itself prove background-thread execution; Sendable conformance alone does not prove thread-safe mutation.
| Concern | Source mechanism | Verified placement or handoff | Evidence |
|---|---|---|---|
| Main isolation | @MainActor |
The cited annotation marks its attached declaration or closure as main-actor isolated. | CanyonCrosser/ECS/HikeSystem.swift:63 |
| Suspension boundary | async declaration or closure |
The source declares or crosses an asynchronous boundary; it does not by itself establish background execution. | CanyonCrosser/Extensions/Entity.swift:85 |
| Transfer contract | Sendable or @Sendable |
The source declares a sendability boundary; this alone does not synchronize mutable state. | CanyonCrosser/Models/AppModel/AppModel.swift:21 |
| Task creation | Task |
The source creates an unstructured task; surrounding context determines inherited actor isolation. | CanyonCrosser/Previews/HikerComponentAppStatePreview.swift:25 |
@MainActor/MainActor.run, DispatchQueue.main, and RunLoop.main are reported as distinct isolation, queue, and event-loop mechanisms. A plain Task is kept separate from Task.detached; neither is labeled as a background thread.
Reference code
CanyonCrosser/ECS/HikeSystem.swift:63 — representative execution boundary
@MainActor
private func animateHiker(deltaTime: TimeInterval, entity: Entity) -> Bool {
let animationTime: TimeInterval = 0.2
// ...
}State propagation, frameworks, and dependencies
Evidence limit: an import proves a source-level compilation dependency at the cited line; it does not prove runtime use, architectural adoption, or whether a Swift package is a direct application dependency.
| Category | Mechanism or module | Verified role | Evidence |
|---|---|---|---|
| State propagation | SwiftUI state property wrapper |
A SwiftUI property wrapper supplies or observes UI state. | CanyonCrosser/CanyonCrosserApp.swift:13 |
| State propagation | @Observable |
Observation macro publishes source-visible changes. | CanyonCrosser/ECS/ClippingMarginPercentageComponent.swift:12 |
| Source import | SwiftUI |
The cited file imports this module; runtime use and architectural role are not inferred. | CanyonCrosser/CanyonCrosserApp.swift:8 |
| Source import | RealityKit |
The cited file imports this module; runtime use and architectural role are not inferred. | CanyonCrosser/ECS/ClippingMarginPercentageComponent.swift:9 |
| Source import | Foundation |
The cited file imports this module; runtime use and architectural role are not inferred. | CanyonCrosser/ECS/HikeSystem.swift:8 |
| Source import | RealityKitContent |
The cited file imports this module; runtime use and architectural role are not inferred. | CanyonCrosser/CanyonCrosserApp.swift:9 |
| Source import | Observation |
The cited file imports this module; runtime use and architectural role are not inferred. | CanyonCrosser/Models/AppModel/AppModel.swift:9 |
| Source import | CoreGraphics |
The cited file imports this module; runtime use and architectural role are not inferred. | Packages/RealityKitContent/Sources/RealityKitContent/TimeOfDayLightSystem.swift:10 |
receive(on:) describes downstream delivery scheduling, while subscribe(on:) describes upstream subscription/request/cancel scheduling. An import Combine alone establishes neither behavior nor a Store, reducer, Redux, or other application architecture.
Class and protocol design
Packages/RealityKitContent/Sources/RealityKitContent/ValueGradient.swift:8 — representative type boundary
protocol Interpolatable<Interpolator> {
associatedtype Interpolator: BinaryFloatingPoint
static func *(lhs: Self, rhs: Interpolator) -> Self
static func *(lhs: Interpolator, rhs: Self) -> Self
static func +(lhs: Self, rhs: Self) -> Self
}| Type | Responsibility | Depends on or conforms to |
|---|---|---|
CanyonCrosserApp |
Application entry and top-level composition | App |
WeatherOrTimeView |
User-interface presentation and input forwarding | View |
AppModel |
Feature data or observable state | Sendable |
AppPhaseModel |
Feature data or observable state | Concrete collaborators/imported frameworks |
ClippingMarginPercentageComponent |
Stores entity-component data or behavior | Component |
CarouselLabelView |
User-interface presentation and input forwarding | View |
CarouselModel |
Feature data or observable state | Concrete collaborators/imported frameworks |
TimeOfDayLightComponent |
Stores entity-component data or behavior | Component, Codable |
TimeOfDayLightSystem |
Runs entity-component-system update logic | System |
TimeOfDayMaterialComponent |
Stores entity-component data or behavior | Component, Codable |
The source explicitly defines local protocol relationships: Double → Interpolatable, Float → Interpolatable, SIMD3<Float> → Interpolatable, SIMD4<Float> → Interpolatable.
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
appModel (CanyonCrosser/CanyonCrosserApp.swift:13) |
private |
Use is restricted to the lexical declaration and same-file extensions allowed by Swift. | Inference: keep state mutation or dependency lifetime inside the owning implementation. |
appPhaseModel (CanyonCrosser/CanyonCrosserApp.swift:14) |
private |
Use is restricted to the lexical declaration and same-file extensions allowed by Swift. | Inference: keep state mutation or dependency lifetime inside the owning implementation. |
appModel (CanyonCrosser/Configuration Options/BreakthroughEffectEditor.swift:10) |
private |
Use is restricted to the lexical declaration and same-file extensions allowed by Swift. | Inference: keep state mutation or dependency lifetime inside the owning implementation. |
cloudsEnabled (CanyonCrosser/Configuration Options/CloudsEditor.swift:12) |
private |
Use is restricted to the lexical declaration and same-file extensions allowed by Swift. | Inference: keep state mutation or dependency lifetime inside the owning implementation. |
Reference code
CanyonCrosser/CanyonCrosserApp.swift:13 — representative boundary
@main
struct CanyonCrosserApp: App {
@State private var appModel: AppModel = AppModel()
// ...
}Swift declarations without a modifier are internal; explicit private, fileprivate, private(set), public, or open entries above are interpreted by language semantics. Objective-C/C samples instead rely on header and implementation boundaries, which are not equivalent to Swift lexical privacy.
Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Application entry and top-level composition | CanyonCrosserApp |
The source’s App suffix makes this role explicit. |
| Stores entity-component data or behavior | ClippingMarginPercentageComponent, FadingCloudComponent, HikePlaybackStateComponent, HikeTimingComponent |
The source’s Component suffix makes this role explicit. |
| Feature data or observable state | AppModel, AppPhaseModel, CarouselModel |
The source’s Model suffix makes this role explicit. |
| Runs entity-component-system update logic | ClippingMarginPercentageSystem, FeatheringSystem, HikeSystem, LightRotationSystem |
The source’s System suffix makes this role explicit. |
| User-interface presentation and input forwarding | BackButtonView, CarouselBodyView, CarouselLabelView, CarouselView |
The source’s View suffix makes this role explicit. |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| Protocol-oriented abstraction | Packages/RealityKitContent/Sources/RealityKitContent/ValueGradient.swift:15 |
A local protocol and concrete conformance create an explicit capability boundary. |
| Binding-based state propagation | CanyonCrosser/Configuration Options/TimelineViewEditor.swift:32 |
A binding exposes controlled read/write access while the upstream owner remains authoritative. |
Main application flow
sequenceDiagram
participant AppModel
participant Entity
participant root
participant grandCanyonEntity
AppModel->>Entity: Entity()
AppModel->>AppModel: rootScene()
AppModel->>root: findAndLoadEntity()
AppModel->>AppModel: findAndLoadEntity()
AppModel->>root: findAndLoadEntity()
AppModel->>root: findAndLoadEntity()
AppModel->>grandCanyonEntity: findAndLoadEntity()
AppModel->>AppModel: loadHikeAssets()
Reference code
CanyonCrosser/Models/AppModel/AppModel+Loading.swift:26 — prepareAssets()
func prepareAssets() async throws {
defer { doAllSetup() }
// Load the root entity, the Grand Canyon scene.
async let rootScene = try await Entity(named: EntityName.grandCanyonScene.rawValue, in: realityKitContentBundle)
root = try await rootScene
grandCanyonEntity = try await root.findAndLoadEntity(named: .grandCanyonEntity, error: .grandCanyon)
let terrainEntity = try await self.grandCanyonEntity.findAndLoadEntity(named: .terrain, error: .terrain)
self.terrainEntityBaseExtents = (terrainEntity.visualBounds(relativeTo: nil).extents) / terrainEntity.scale(relativeTo: nil)
sunlight = try await root.findAndLoadEntity(named: .sunlight, error: .sunlight)
birdsEntity = try await root.findAndLoadEntity(named: .birds, error: .birds)
cloudsEntity = try await grandCanyonEntity.findAndLoadEntity(named: .clouds, error: .clouds)
hikerEntity = try await grandCanyonEntity.findAndLoadEntity(named: .hiker, error: .hiker)
hikeEntities = hikes.reduce(into: [Hike: [Entity]]()) { partialResult, hike in
partialResult[hike] = []
}
await loadHikeAssets()
}Naming conventions
- Types: App: CanyonCrosserApp; Component: ClippingMarginPercentageComponent, FadingCloudComponent, HikePlaybackStateComponent, HikeTimingComponent, HikerDragStateComponent; Model: AppModel, AppPhaseModel, CarouselModel; System: ClippingMarginPercentageSystem, FeatheringSystem, HikeSystem, LightRotationSystem, TimeOfDayLightSystem; View: BackButtonView, CarouselBodyView, CarouselLabelView, CarouselView, CompactControlsView.
- Protocols:
Interpolatable. - Methods:
fadeInClouds,fadeOutClouds,getHikerAnimationPlaybackController,setHikerVisibility,updateNormalizedZPositions,interpolateColor,interpolateIntensity. - Files:
CanyonCrosser/CanyonCrosserApp.swift,CanyonCrosser/Views/Progress Slider/WeatherOrTimeView.swift,CanyonCrosser/Models/AppModel/AppModel.swift,CanyonCrosser/Models/AppPhaseModel.swift,CanyonCrosser/ECS/ClippingMarginPercentageComponent.swift,CanyonCrosser/Views/Carousel/CarouselLabelView.swift.
Architecture takeaways
CanyonCrosserAppis the main source-visible entry or composition anchor for this sample.- Framework work reaches SwiftUI, RealityKit, RealityKitContent, CoreGraphics through a deliberately small high-level chain; the detailed API graph remains inside the cited implementation files.
- Stored-property evidence identifies lifecycle collaboration; it does not by itself prove exclusive object ownership.
- Access-control conclusions separate verified language visibility from the likely design rationale.
- Local protocol relationships provide an explicit substitution boundary.
Source map
| Source file | Relevant symbols |
|---|---|
CanyonCrosser/CanyonCrosserApp.swift |
Cited implementation, SwiftUI state property wrapper, SwiftUI, RealityKitContent, CanyonCrosserApp |
Packages/RealityKitContent/Sources/RealityKitContent/ValueGradient.swift |
Interpolatable, Cited implementation, ValueGradient |
CanyonCrosser/Configuration Options/BreakthroughEffectEditor.swift |
Cited implementation, BreakthroughEffectEditor |
CanyonCrosser/Configuration Options/CloudsEditor.swift |
Cited implementation, CloudsEditor |
CanyonCrosser/Configuration Options/TimelineViewEditor.swift |
Cited implementation, TimelineViewEditor, ContentAlignmentSelector, SceneAnchorSelector, ToggleButton |
CanyonCrosser/ECS/HikeSystem.swift |
@MainActor, Foundation, HikeSystem |
CanyonCrosser/Extensions/Entity.swift |
async declaration or closure, Feature implementation |
CanyonCrosser/Models/AppModel/AppModel.swift |
Sendable or @Sendable, Observation, LoadingError, AppModel |
CanyonCrosser/Previews/HikerComponentAppStatePreview.swift |
Task, HikerComponentAppModelData |
CanyonCrosser/ECS/ClippingMarginPercentageComponent.swift |
@Observable, RealityKit, ClippingMarginPercentageComponent, Environment, Values |
Packages/RealityKitContent/Sources/RealityKitContent/TimeOfDayLightSystem.swift |
CoreGraphics, TimeOfDayLightSystem |
CanyonCrosser/Views/Progress Slider/WeatherOrTimeView.swift |
WeatherOrTimeView, Display, WeatherAndTime, TimeLabel, WeatherLabel |
CanyonCrosser/Models/AppPhaseModel.swift |
AppPhaseModel, AppPhase |
CanyonCrosser/Views/Carousel/CarouselLabelView.swift |
CarouselLabelView, CarouselLabelText, HiddenLabelText |
CanyonCrosser/Models/CarouselModel.swift |
CarouselModel |
Packages/RealityKitContent/Sources/RealityKitContent/TimeOfDayLightComponent.swift |
TimeOfDayLightComponent |
CanyonCrosser/Models/AppModel/AppModel+Loading.swift |
prepareAssets |