Sample CodevisionOSReviewed 2026-07-21View on Apple Developer

Simulating particles in your visionOS app

At a glance

Item Summary
Purpose Add a range of visual effects to a RealityKit view by attaching a particle emitter component to an entity.
App architecture ParticleEmitterApp presents MainView; views create and tune particle-emitter entities while reusable gesture components and GeneralGoalSystem keep entity interaction outside the SwiftUI hierarchy.
Main patterns SwiftUI scene composition, UI–RealityKit bridge, Entity-component-system, Entity-scoped particle configuration
Project style Code-rich sample with 12 scanned Swift file(s) and 1328 implementation line(s).

Project structure

Source bundle/
├── Particle Emitter/
│   ├── MainView.swift  # MainView
│   ├── EmitterView.swift  # EmitterView
│   ├── ParticleEmitterApp.swift  # ParticleEmitterApp
│   └── EmitterSettings.swift  # EmitterSettings, EmitterPresets, ColorSetting
└── Packages/
    └── RealityGestures/
        └── Sources/
            └── RealityGestures/
                ├── RealityDragComponent.swift  # RealityDragComponent, MoveConstraint, DragComponentType
                ├── GeneralGoalComponent.swift  # GeneralGoalComponent, GeneralGoal, GeneralGoalSystem
                └── RealityTapComponent.swift  # RealityTapComponent

Structure observations

  • The runtime boundary is WindowGroup; 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

Particle Emitter/ParticleEmitterApp.swift:11 — executable or app-lifecycle anchor.

struct ParticleEmitterApp: App {
    /// Stores settings for the particle emitter.
    @ObservedObject public var emitterSettings = EmitterSettings()

    var body: some Scene {
        WindowGroup {
            MainView()
        }.environment(emitterSettings)
    }
}

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

Particle Emitter/ParticleEmitterApp.swift:13 — representative stored state or nearest verified lifecycle anchor.

@main
struct ParticleEmitterApp: App {
    // ...
    @ObservedObject public var emitterSettings = EmitterSettings()
    // ...
}
Owner Object or state Relationship Mutation authority
ParticleEmitterApp EmitterSettings as emitterSettings receives a shared or non-owning reference The upstream owner controls lifetime; this scope may invoke the exposed mutable API
EmitterSettings Entity as emitterEntity creates and retains The owning type coordinates writes
MainView EmitterSettings as emitterSettings receives a shared or non-owning reference The upstream owner controls lifetime; this scope may invoke the exposed mutable API
GeneralGoalSystem EntityQuery as generalGoalQuery creates and retains The owning type coordinates 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
ParticleEmitterApp (Particle Emitter/ParticleEmitterApp.swift:11) Declares app lifecycle and top-level dependency lifetime. App
MainView (Particle Emitter/MainView.swift:14) Presents UI and forwards gestures or lifecycle events. View
GeneralGoalSystem (Packages/RealityGestures/Sources/RealityGestures/GeneralGoalComponent.swift:47) Updates entities matching a RealityKit component query. System
GeneralGoalComponent (Packages/RealityGestures/Sources/RealityGestures/GeneralGoalComponent.swift:13) Stores RealityKit entity data. Component, Codable
RealityDragComponent (Packages/RealityGestures/Sources/RealityGestures/RealityDragComponent.swift:13) Stores RealityKit entity data. Component
RealityTapComponent (Packages/RealityGestures/Sources/RealityGestures/RealityTapComponent.swift:30) Stores RealityKit entity data. Component
EmitterView (Particle Emitter/EmitterView.swift:13) 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 var updated: ((EntityTargetValue<DragGesture.Value>, RealityDra... (Packages/RealityGestures/Sources/RealityGestures/SwiftUI+RealityGestures.swift:35) private Use stays inside the declaration and same-file extensions permitted by Swift. Inference: Hide lifecycle-sensitive state or an implementation step.
fileprivate static func getClampedPosition( (Packages/RealityGestures/Sources/RealityGestures/RealityDragComponent.swift:157) fileprivate Use is limited to declarations in this source file. Inference: Share with same-file helpers without making the symbol module-wide.
public func dragEnded(_ entity: Entity, worldPos: SIMD3<Float>) { (Packages/RealityGestures/Sources/RealityGestures/RealityDragComponent+DragEvents.swift:76) public The symbol is available to importing modules, subject to its containing type’s visibility. Inference: Expose an intentional package or cross-target surface.
internal func handleMoveState( (Packages/RealityGestures/Sources/RealityGestures/RealityDragComponent.swift:177) internal With no narrower modifier, Swift keeps the declaration internal to the module. Inference: Allow app-target collaboration without exporting a library API.

No reviewed Swift access example uses private(set), open; declarations without a modifier are internal.

Reference code

Packages/RealityGestures/Sources/RealityGestures/SwiftUI+RealityGestures.swift:35 — representative visibility boundary.

public struct RealityDragGesture: Gesture {
    // ...
    private var updated: ((EntityTargetValue<DragGesture.Value>, RealityDragComponent.DragStatus) -> Void)?
    // ...
}

Logic ownership and placement

Logic Owning type or file Placement rationale
Application/command lifecycle ParticleEmitterApp The executable boundary determines app, controller, scene, or command lifetime.
Presentation and user intent MainView The view/controller forwards input and owns only presentation-local work.
Framework/session/render coordination GeneralGoalSystem The role-named collaborator isolates long-lived or per-frame work from presentation code.
Per-entity data and repeated behavior Packages/RealityGestures/Sources/RealityGestures/GeneralGoalComponent.swift:47 Components carry data; the system queries and updates matching entities.

Design patterns

Pattern Source evidence Purpose or tradeoff
SwiftUI scene composition Particle Emitter/ParticleEmitterApp.swift:11 Keeps windows, volumes, and immersive presentation visible at the app boundary.
UI–RealityKit bridge Particle Emitter/EmitterView.swift:28 Builds or hosts the RealityKit entity graph from SwiftUI or a platform view/controller lifecycle.
Entity-component-system Packages/RealityGestures/Sources/RealityGestures/GeneralGoalComponent.swift:47 Stores per-entity data in components and advances repeated behavior in registered systems.
Entity-scoped particle configuration Particle Emitter/EmitterSettings.swift:17 Keeps emitter parameters on the entity so views can tune effects without introducing a parallel simulation model.

Naming conventions

  • Role suffixes are evidence, not decoration: App: ParticleEmitterApp; System: GeneralGoalSystem; Component: GeneralGoalComponent, RealityDragComponent, RealityTapComponent; View: EmitterView, MainView.
  • ECS names pair data and behavior: components GeneralGoalComponent, RealityDragComponent, RealityTapComponent; systems GeneralGoalSystem.
  • Protocols: no app-defined protocol in the reviewed source.
  • Commands use verb-led methods: buildEmitterContent, updateEmitterBounds, getCollisionPoints, getClampedPosition, getClampedRotation, handleMoveState, handleRotationState, closestPoint.
  • 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 ParticleEmitterApp 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.

Source map

Source file Relevant symbols
Particle Emitter/MainView.swift:14 MainView
Particle Emitter/EmitterView.swift:13 EmitterView
Particle Emitter/ParticleEmitterApp.swift:11 ParticleEmitterApp
Packages/RealityGestures/Sources/RealityGestures/RealityDragComponent.swift:13 RealityDragComponent, MoveConstraint, DragComponentType, DragStatus, DragData
Packages/RealityGestures/Sources/RealityGestures/GeneralGoalComponent.swift:13 GeneralGoalComponent, GeneralGoal, GeneralGoalSystem
Packages/RealityGestures/Sources/RealityGestures/RealityTapComponent.swift:30 RealityTapComponent
Particle Emitter/EmitterSettings.swift:16 EmitterSettings, EmitterPresets, ColorSetting