Sample CodeiOS 27.0 beta, iPadOS 27.0 beta, Mac Catalyst 27.0 beta, Xcode 27.0 betaReviewed 2026-07-23View on Apple Developer

AVCam: Building a camera app

At a glance

Item Summary
Purpose Capture photos and movies, expose capture features to SwiftUI, and save media to Photos.
App architecture SwiftUI MVVM-style app: CameraView consumes the Camera capability, CameraModel owns UI state, and actor services own capture and library work.
Main patterns Protocol-oriented view model, actor isolation, composed output services, UIKit preview adapter, async streams/observation.
Project style Feature folders for Capture, Model, Views, Support, plus main app, locked-camera capture extension, and Control Center extension targets.

Project structure

AVCam/
├── AVCamApp.swift
├── CameraModel.swift
├── CameraView.swift
├── CaptureService.swift
├── Capture/
│   ├── DeviceLookup.swift
│   ├── PhotoCapture.swift
│   ├── MovieCapture.swift
│   └── SPCObserver.swift
├── Model/
│   ├── Camera.swift
│   ├── CameraState.swift
│   ├── DataTypes.swift
│   ├── Intent/AVCamCaptureIntent.swift
│   └── MediaLibrary.swift
├── Views/
│   ├── CameraPreview.swift
│   ├── Controls/
│   ├── Overlays/
│   └── Toolbars/
└── Support/
AVCamCaptureExtension/
AVCamControlCenterExtension/

Structure observations

  • Capture mechanics are separated from observable screen state and from SwiftUI view composition.
  • PhotoCapture and MovieCapture isolate output-specific behavior under Capture/; shared app-facing contracts and value types live under Model/.
  • Preview bridging lives with views because it adapts an AVCaptureVideoPreviewLayer-backed UIView to SwiftUI.
  • The app, capture extension, and Control Center extension are separate targets but share model intent/state concepts.

Overall architecture

Reference code

AVCam/AVCamApp.swift:13 — composition root

struct AVCamApp: App {
    @State private var camera = CameraModel()

    var body: some Scene {
        WindowGroup {
            CameraView(camera: camera)
                .task {
                    await camera.start()
                }
        }
    }
}

Interpretation

AVCamApp is the composition root and long-lived owner of CameraModel. The model mediates all view intents and observable state; it delegates capture and photo-library work instead of owning framework details. Calling this MVVM is an architectural interpretation supported by the source comments and dependency direction, not a type named ViewModel.

Ownership and state

Ownership evidence

AVCam/CameraModel.swift:63 — model dependencies

private let mediaLibrary = MediaLibrary()
private let captureService = CaptureService()
private var cameraState = CameraState()
Owner Object or state Relationship Mutation authority
AVCamApp CameraModel Creates and retains it with SwiftUI @State CameraModel mutates its observable state on MainActor.
CameraModel UI-facing status, activity, readiness, thumbnail, error Publishes read-only state to views Setters are restricted to CameraModel.
CameraModel CaptureService, MediaLibrary, CameraState Creates and retains each collaborator Model sends intents; actors own their internal state.
CaptureService Capture session, device input, photo/movie services Creates, configures, and retains capture graph CaptureService actor on its serial executor.
PhotoCapture / MovieCapture Their AVCaptureOutput and delegate lifecycle Own output-specific capture work Each service mutates its own internals.

Class and protocol design

Reference code

AVCam/CameraView.swift:12 — protocol-oriented view dependency

@MainActor
struct CameraView<CameraModel: Camera>: PlatformView {
    @State var camera: CameraModel
    // ...
}
Type Responsibility Depends on
Camera Define the complete view-facing camera capability App data types and PreviewSource
CameraModel Translate UI intent to services and service streams to observable state CaptureService, MediaLibrary, CameraState
CaptureService Configure and serialize the capture graph AVFoundation, output services, device lookup/observers
OutputService Normalize output, activity, capabilities, rotation, and reconfiguration AVCaptureOutput
PreviewSource / PreviewTarget Connect preview without exposing capture internals to SwiftUI AVCaptureSession only at the target boundary

Access control

Symbol Access Verified effect Likely rationale
CameraModel.status, activity, readiness, thumbnail, error private(set) Views can read state but only the model can mutate it Preserves the model as the single UI-state writer.
CameraModel.mediaLibrary and captureService private let Neither dependency can be replaced or accessed by views Keeps service lifecycle and commands behind the Camera interface.
CaptureService.captureSession, output services, and inputs private Capture graph mutation stays inside the actor Protects ordering and actor/queue confinement.
CaptureService.previewSource nonisolated let, implicit internal Clients can connect a preview without awaiting actor isolation Exposes a narrow immutable bridge, not the session itself.
App-defined types/protocols implicit internal Shared only among targets/files in the built module This is an app sample, not a public framework API.

Reference code

AVCam/CameraModel.swift:24 — read-only view state

private(set) var status = CameraStatus.unknown
private(set) var captureActivity = CaptureActivity.idle
private(set) var thumbnail: CGImage?
private(set) var error: Error?

No public, open, or fileprivate declaration appears in the AVCam Swift sources.

Logic ownership and placement

Logic Owning type or file Placement rationale
View composition and gestures CameraView and Views/ SwiftUI-only layout and user interactions.
UI state and intent translation CameraModel Main-actor observable boundary consumed by views.
Session/device configuration CaptureService Serialized off-main AVFoundation ownership.
Photo/movie mechanics PhotoCapture, MovieCapture One object per output capability and delegate lifecycle.
Photos authorization/save/thumbnail MediaLibrary Separate actor and framework boundary.
Persistent capture preferences CameraState Codable state shared with the capture extension.

Design patterns

Pattern Source evidence Purpose or tradeoff
MVVM-style mediation AVCam/CameraModel.swift:13 and class comment Keeps SwiftUI independent of capture implementation.
Protocol-oriented substitution AVCam/Model/Camera.swift:13 and PreviewCameraModel conformance Lets previews use a non-hardware camera model.
Actor-isolated service AVCam/CaptureService.swift:12 and custom executor at line 73 Serializes blocking capture operations away from the main actor.
Output-service composition AVCam/CaptureService.swift:35 and outputServices Shares capability/rotation handling while retaining specialized photo/movie objects.
Adapter/bridge AVCam/Views/CameraPreview.swift:11 and UIViewRepresentable Hosts an AVFoundation preview layer inside SwiftUI.
Delegate-to-async adaptation AVCam/Capture/PhotoCapture.swift:43 Wraps callback capture in a checked continuation.

Main application flow

Reference code

AVCam/CameraModel.swift:153 — photo flow coordinator

func capturePhoto() async {
    do {
        let photo = try await captureService.capturePhoto(with: currentPhotoFeatures)
        try await mediaLibrary.save(photo: photo)
    } catch {
        self.error = error
    }
}

Naming conventions

  • Types: role nouns (CameraModel, CaptureService, MediaLibrary) and feature-specific services (PhotoCapture, MovieCapture).
  • Protocols: capability or endpoint nouns (Camera, OutputService, PreviewSource, PreviewTarget).
  • Methods: user commands (capturePhoto, toggleRecording, switchVideoDevices) and setup verbs (setUpSession, configureControls).
  • State: Boolean names use is, should, or prefers; state snapshots use noun types such as CaptureActivity and CaptureCapabilities.
  • Files: primary type per file; folders group feature mechanics, model contracts, views, and extensions.

Architecture takeaways

  • The main actor owns presentation state; a dedicated actor owns the AVFoundation graph and its serial executor.
  • Views depend on a Camera capability, allowing Simulator/preview substitution without conditional UI architecture.
  • Output-specific delegates and state stay in PhotoCapture and MovieCapture, preventing CaptureService from becoming the only implementation class.
  • A narrow preview-source protocol crosses the UI/capture boundary without publishing the capture session.
  • private(set) makes observable state readable while keeping mutation authority explicit.

Source map

Source file Relevant symbols
AVCam/AVCamApp.swift AVCamApp, composition root
AVCam/CameraModel.swift CameraModel, UI state and intent coordination
AVCam/CaptureService.swift CaptureService, capture graph ownership
AVCam/Model/Camera.swift Camera protocol
AVCam/Model/DataTypes.swift OutputService, state/value types
AVCam/Capture/PhotoCapture.swift PhotoCapture, delegate-to-async bridge
AVCam/Capture/MovieCapture.swift MovieCapture
AVCam/Model/MediaLibrary.swift MediaLibrary
AVCam/Views/CameraPreview.swift Preview protocols and SwiftUI/UIKit adapter