Sample CodeiOS 27.0 beta+, iPadOS 27.0 beta+, Mac Catalyst 27.0 beta+Reviewed 2026-07-21View on Apple Developer

Supporting Center Stage front camera in your iOS app

At a glance

Item Summary
Purpose Capture photos and movies while applying Center Stage and smart-framing recommendations.
App architecture SwiftUI views depend on a Camera protocol; observable CameraModel mediates UI state; a custom-executor CaptureService actor owns AVFoundation.
Main patterns MVVM, actor-isolated service, protocol-oriented substitution, output-service strategies, AsyncStream observation.
Project style Feature folders split Capture, Model, Views, and Support, with smaller toolbar/overlay view groups.

Project structure

CenterStageFrontCamera/CenterStageFrontCam/
├── CenterStageFrontCam.swift
├── CameraModel.swift
├── CaptureService.swift
├── Capture/
│   ├── DeviceLookup.swift
│   ├── PhotoCapture.swift
│   └── MovieCapture.swift
├── Model/
│   ├── Camera.swift
│   ├── CameraState.swift
│   └── DataTypes.swift
├── Views/
│   ├── CameraUI.swift
│   ├── Controls/
│   ├── Overlays/
│   └── Toolbars/
└── Support/

The source separates observable app state from capture mechanics. PreviewCameraModel is a protocol-compatible fake kept under Preview Content.

Overall architecture

Reference code

CenterStageFrontCamera/CenterStageFrontCam/CameraModel.swift:12 — the source explicitly defines the mediator boundary (abridged).

@Observable
final class CameraModel: Camera {
    // ...
}

Calling this MVVM is an interpretation, but it is strongly supported by this declared mediation role and the model-driven generic views.

Ownership and state

Ownership evidence

CenterStageFrontCamera/CenterStageFrontCam/CameraModel.swift:23 — read-only UI state and private owned collaborators (abridged).

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

private let mediaLibrary = MediaLibrary()
private let captureService = CaptureService()
private var cameraState = CameraState()
Owner Object or state Relationship Mutation authority
CenterStageFrontCam CameraModel @State app-lifetime owner App creates; CameraModel mutates observable state.
CameraModel UI-facing status/activity/settings Main-actor observable state Model methods and property observers update it.
CaptureService Session, inputs, output services, smart-framing observation Actor-owned capture graph Actor serializes mutations on its custom session-queue executor.
PhotoCapture / MovieCapture Concrete output and delegate lifecycle Owned strategies Each output service mutates its own capture-specific state.
MediaLibrary Photo-library writes and thumbnail stream Owned collaborator Library performs persistence; model forwards thumbnail state.

Class and protocol design

Reference code

CenterStageFrontCamera/CenterStageFrontCam/Model/Camera.swift:11 and CenterStageFrontCamera/CenterStageFrontCam/Model/DataTypes.swift:170 — UI substitution and capture-output capabilities are separate protocols (abridged).

@MainActor
protocol Camera: AnyObject, SendableMetatype {
    var status: CameraStatus { get }
    var previewSource: PreviewSource { get }
    func start() async
    func capturePhoto() async
    func toggleRecording() async
}

protocol OutputService {
    associatedtype Output: AVCaptureOutput
    nonisolated var output: Output { get }
    nonisolated func updateConfiguration(for device: AVCaptureDevice)
}

The first protocol makes views previewable without hardware. The second lets CaptureService configure heterogeneous photo/movie outputs through shared behavior.

Access control

Symbol Access Verified effect Likely rationale
CameraModel.status, activity, thumbnail private(set) Views can observe but cannot forge capture results. Keeps mutation authority in the mediator.
CameraModel.captureService private UI and other files cannot bypass the model. Maintains one UI-to-capture command path.
CaptureService.captureSession, outputs private Only the actor configures the capture graph. Protects AVFoundation ordering and actor invariants.
Activity streams and previewSource nonisolated let Consumers can obtain immutable handles without an actor hop. Safe publication of fixed endpoints.
Selected AVFoundation references nonisolated(unsafe) Compiler isolation checking is explicitly bypassed for non-Sendable framework objects. Interop escape hatch; not general public access.

CenterStageFrontCamera/CenterStageFrontCam/CaptureService.swift:13 (abridged):

actor CaptureService {
    private(set) var captureActivity: CaptureActivity = .idle
    nonisolated let captureActivityStream: AsyncStream<CaptureActivity>
    nonisolated let previewSource: PreviewSource
    private let captureSession = AVCaptureSession()
    private let photoCapture = PhotoCapture()
    private let movieCapture = MovieCapture()
}

Logic ownership and placement

Logic Owning type or file Placement rationale
UI state and intent mediation CameraModel Translates view actions into async service calls and publishes results.
Session/device/smart-framing mutation CaptureService Confines non-thread-safe AVFoundation operations to one actor executor.
Photo and movie details PhotoCapture, MovieCapture Each service owns its output and framework delegate lifecycle.
Photos persistence MediaLibrary Keeps system library access outside capture setup.
View composition Views/ Generic views require only the Camera capability.

Design patterns

Pattern Source evidence Purpose or tradeoff
MVVM CenterStageFrontCamera/CenterStageFrontCam/CameraModel.swift:12 Observable mediator separates SwiftUI from capture implementation; label is an inference.
Actor/service CenterStageFrontCamera/CenterStageFrontCam/CaptureService.swift:13 Serializes capture graph changes off the main actor.
Protocol-oriented substitution CenterStageFrontCamera/CenterStageFrontCam/Model/Camera.swift:11 Real and preview models share a view contract.
Strategy CenterStageFrontCamera/CenterStageFrontCam/Model/DataTypes.swift:170 Photo/movie output services share configuration behavior.
Async sequence observation CenterStageFrontCamera/CenterStageFrontCam/CameraModel.swift:198 Model consumes activity, thumbnail, and control-state streams without exposing continuations.

Naming conventions

  • Protocols name capabilities (Camera, OutputService, PreviewSource, PreviewTarget).
  • Concrete types use responsibility nouns (CaptureService, MediaLibrary, DeviceLookup).
  • Commands are verbs (start, capturePhoto, toggleRecording, applyRecommendedFraming).
  • State uses is/should/prefers; private(set) marks externally readable results.
  • Files generally match their primary type; view subfolders group Controls, Overlays, and Toolbars.

Architecture takeaways

  • Put the observable main-actor model between SwiftUI and the actor-owned capture graph.
  • Use capability protocols where there is a real substitute: previews and heterogeneous capture outputs.
  • Publish immutable streams/preview endpoints from the actor; retain mutation inside it.
  • Keep photo/movie capture and Photos persistence as distinct owners.

Source map

Source file Relevant symbols
CenterStageFrontCamera/CenterStageFrontCam/CenterStageFrontCam.swift App ownership/startup
CenterStageFrontCamera/CenterStageFrontCam/Model/Camera.swift UI-facing protocol
CenterStageFrontCamera/CenterStageFrontCam/CameraModel.swift Observable mediator
CenterStageFrontCamera/CenterStageFrontCam/CaptureService.swift Actor/session/smart framing
CenterStageFrontCamera/CenterStageFrontCam/Model/DataTypes.swift OutputService

Source caveat: the catalog marks the required iOS, iPadOS, Mac Catalyst, and Xcode 27 APIs as beta at review time.