Sample CodeiOS, iPadOS, Mac CatalystReviewed 2026-07-21View on Apple Developer

Scanning objects using Object Capture

At a glance

Item Summary
Purpose Implement a full scanning workflow for capturing objects on iOS devices.
App architecture GuidedCaptureSampleApp injects the singleton AppDataModel; feature views drive onboarding and capture while the model owns ObjectCaptureSession state and CaptureFolderManager owns session-folder lifecycle.
Main patterns SwiftUI scene composition, Observable state owner, Session owner boundary, Async update consumer, Protocol capability boundary, Capture-session state machine
Project style Code-rich sample with 27 scanned Swift file(s) and 3961 implementation line(s).

Project structure

Source bundle/
└── GuidedCaptureSample/
    └── GuidedCaptureSample/
        ├── AppDataModel.swift  # AppDataModel, ModelState, CaptureMode
        ├── CaptureFolderManager.swift  # CaptureFolderManager, Error
        ├── Views/
        │   ├── ContentView.swift  # ContentView
        │   ├── CaptureOverlayView.swift  # CaptureOverlayView, TutorialView, LocalizedString
        │   ├── ModelView.swift  # ModelView, ARQuickLookController, Coordinator
        │   └── PlayerView.swift  # PlayerView, Coordinator, AVPlayerView
        └── GuidedCaptureSampleApp.swift  # GuidedCaptureSampleApp

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

GuidedCaptureSample/GuidedCaptureSample/GuidedCaptureSampleApp.swift:11 — executable or app-lifecycle anchor.

struct GuidedCaptureSampleApp: App {
    static let subsystem: String = "com.example.apple-samplecode.guided-capture-sample"

    var body: some Scene {
        WindowGroup {
            ContentView()
                .environment(AppDataModel.instance)
        }
    }
}

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

GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:35 — representative stored state or nearest verified lifecycle anchor.

    private(set) var photogrammetrySession: PhotogrammetrySession?

    /// When we start a new capture, the folder will be set here.
    private(set) var captureFolderManager: CaptureFolderManager?

    /// Shows whether the user decided to skip reconstruction.
    private(set) var isSaveDraftEnabled = false
Owner Object or state Relationship Mutation authority
AppDataModel PhotogrammetrySession as photogrammetrySession stores and coordinates The declaring type controls writes
AppDataModel CaptureFolderManager as captureFolderManager stores and coordinates The declaring type controls writes
AppDataModel AppDataModel as instance creates and retains The owning type coordinates writes
AppDataModel CaptureMode as captureMode owns value state 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
GuidedCaptureSampleApp (GuidedCaptureSample/GuidedCaptureSample/GuidedCaptureSampleApp.swift:11) Declares app lifecycle and top-level dependency lifetime. App
ContentView (GuidedCaptureSample/GuidedCaptureSample/Views/ContentView.swift:14) Presents UI and forwards gestures or lifecycle events. View
AppDataModel (GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:18) Owns feature state and domain transitions. Identifiable
OverlayButtons (GuidedCaptureSample/GuidedCaptureSample/Views/CaptureOverlayView.swift:237) Declares a replaceable capability or collaboration contract. Concrete framework collaborators
Coordinator (GuidedCaptureSample/GuidedCaptureSample/Views/ModelView.swift:40) Coordinates long-lived framework or cross-view work. NSObject, QLPreviewControllerDataSource, QLPreviewControllerDelegate
ARQuickLookController (GuidedCaptureSample/GuidedCaptureSample/Views/ModelView.swift:23) Coordinates long-lived framework or cross-view work. UIViewControllerRepresentable
CaptureFolderManager (GuidedCaptureSample/GuidedCaptureSample/CaptureFolderManager.swift:18) Coordinates long-lived framework or cross-view work. Concrete framework collaborators
AVPlayerView (GuidedCaptureSample/GuidedCaptureSample/Views/PlayerView.swift:99) Presents UI and forwards gestures or lifecycle events. UIView

Verified protocol-oriented seams: BottomOverlayButtonsOverlayButtons, TopOverlayButtonsOverlayButtons. Framework conformances remain adapters, not proof of an app-wide protocol architecture.

Access control

Symbol Access Verified effect Likely rationale
private(set) var photogrammetrySession: PhotogrammetrySession? (GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:35) 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 let logger = Logger(subsystem: GuidedCaptureSampleApp.subsystem, (GuidedCaptureSample/GuidedCaptureSample/AppDataModel.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

GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:35 — representative visibility boundary.

    private(set) var photogrammetrySession: PhotogrammetrySession?

    /// When we start a new capture, the folder will be set here.
    private(set) var captureFolderManager: CaptureFolderManager?

    /// Shows whether the user decided to skip reconstruction.
    private(set) var isSaveDraftEnabled = false

    var messageList = TimedMessageList()

    enum ModelState {
        // ...
    }

Logic ownership and placement

Logic Owning type or file Placement rationale
Application/command lifecycle GuidedCaptureSampleApp 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 AppDataModel A stable owner prevents view recomputation or callbacks from duplicating transitions.
Capture/reconstruction session lifecycle GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:22 Session creation and output handling stay outside render-only view code.

Design patterns

Pattern Source evidence Purpose or tradeoff
SwiftUI scene composition GuidedCaptureSample/GuidedCaptureSample/GuidedCaptureSampleApp.swift:11 Keeps windows, volumes, and immersive presentation visible at the app boundary.
Observable state owner GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:18 Keeps shared feature state in a stable owner rather than in transient view values.
Session owner boundary GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:22 Keeps authorization, session lifetime, and framework callbacks in a stable model, manager, or controller.
Async update consumer GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:163 Consumes long-lived framework output in a cancellable asynchronous task.
Protocol capability boundary GuidedCaptureSample/GuidedCaptureSample/Views/BottomOverlayButtons.swift:14 Defines a local capability with concrete conformers; this is the verified protocol-oriented seam.
Capture-session state machine GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:22 Centralizes session lifecycle and exposes explicit capture states to the many guidance and control views.

Naming conventions

  • Role suffixes are evidence, not decoration: App: GuidedCaptureSampleApp; Model: AppDataModel; Coordinator: Coordinator; Controller: ARQuickLookController; Manager: CaptureFolderManager.
  • Protocols: OverlayButtons.
  • Commands use verb-led methods: endCapture, removeCaptureFolder, setShowOverlaySheets, saveDraft, attachListeners, detachListeners, handleAppTermination, startNewCapture.
  • 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 GuidedCaptureSampleApp 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.
  • Tie asynchronous session/output consumers to an explicit owner so cancellation and teardown follow the same lifecycle.

Source map

Source file Relevant symbols
GuidedCaptureSample/GuidedCaptureSample/AppDataModel.swift:18 AppDataModel, ModelState, CaptureMode
GuidedCaptureSample/GuidedCaptureSample/CaptureFolderManager.swift:18 CaptureFolderManager, Error
GuidedCaptureSample/GuidedCaptureSample/Views/ContentView.swift:14 ContentView
GuidedCaptureSample/GuidedCaptureSample/GuidedCaptureSampleApp.swift:11 GuidedCaptureSampleApp
GuidedCaptureSample/GuidedCaptureSample/Views/CaptureOverlayView.swift:15 CaptureOverlayView, TutorialView, LocalizedString, BoundingBoxGuidanceView, OverlayButtons
GuidedCaptureSample/GuidedCaptureSample/Views/ModelView.swift:14 ModelView, ARQuickLookController, Coordinator, QLPreviewControllerWrapper
GuidedCaptureSample/GuidedCaptureSample/Views/PlayerView.swift:11 PlayerView, Coordinator, AVPlayerView