Building a responsive camera app that launches quickly
At a glance
| Item | Summary |
|---|---|
| Purpose | Show the camera preview sooner by deferring capture-output startup and holding back noncritical controls until the session is running. |
| App architecture | An AVCam-based project (AVCam.xcodeproj) with three targets. A @MainActor CameraModel drives a CaptureService actor that configures the session with deferred start. SwiftUI controls are revealed when CameraModel.status becomes .running. |
| Main patterns | Actor isolation (custom serial executor), protocol-oriented abstraction (Camera, OutputService, PreviewSource/PreviewTarget), delegate callbacks bridged to async, and @Published state consumed as async sequences. |
| Project style | Swift sources grouped by role (Capture/, Model/, Views/, Support/). The launch-specific code is a small number of edits spread across CaptureService, PhotoCapture, MovieCapture, one View extension, and four control views. |
| Execution model | CameraModel is @MainActor. CaptureService is an actor whose executor is the serial DispatchSerialQueue sessionQueue, so setUpSession() and the blocking startRunning() run as jobs on that queue. The main actor is suspended at await, not blocked. |
| State/event model | @Observable model state (status) gates control visibility through a hidden(until:) modifier. Actor @Published properties are read through .values. scenePhase drives a preview cover. |
| Key frameworks/packages | AVFoundation, SwiftUI, Combine, Photos, LockedCameraCapture, ExtensionKit, AppIntents, WidgetKit. The project declares no Swift package. |
Platform boundary: the DocC record lists iOS, iPadOS, and Mac Catalyst. In the downloaded project, every target sets SUPPORTS_MACCATALYST = NO with SDKROOT = iphoneos (AVCam.xcodeproj/project.pbxproj:945), and the deployment target is iOS 26.0 (AVCam.xcodeproj/project.pbxproj:936), while the DocC record lists 27.0. The bundled README.md is the generic AVCam overview (README.md:1), so this analysis relies on the source code and its comments for the launch-specific behavior.
Project structure
AVCam.xcodeproj/ (3 targets, no Swift packages)
AVCam/ app sources (also compiled into the capture extension)
├── AVCamApp.swift @main App; .task { await camera.start() }
├── CameraModel.swift @MainActor @Observable; sets status = .running
├── CaptureService.swift actor; deferred-start configuration in setUpSession()
├── Capture/
│ ├── PhotoCapture.swift enables responsive capture on the photo output
│ └── MovieCapture.swift configures the video connection before recording
├── Model/ Camera protocol, CameraState, DataTypes, MediaLibrary actor
├── Support/ViewExtensions.swift View.hidden(until:)
└── Views/
├── PreviewContainer.swift background cover keyed to scenePhase
├── CameraUI.swift window size from onGeometryChange
├── Controls/CaptureModeView.swift
└── Toolbars/ MainToolbar, FeatureToolbar, CaptureButton
AVCamCaptureExtension/ LockedCameraCaptureExtension entry
AVCamControlCenterExtension/ ControlWidget bundle
Structure observations
- The project has three native targets:
AVCam(application),AVCamCaptureExtension(ExtensionKit extension), andAVCamControlCenterExtension(app extension) (AVCam.xcodeproj/project.pbxproj:445). The capture extension compiles the sharedAVCamsources, includingCaptureService.swiftandCameraModel.swift(AVCam.xcodeproj/project.pbxproj:525,AVCam.xcodeproj/project.pbxproj:542). It creates its ownCameraModeland starts it the same way the app does (AVCamCaptureExtension/AVCamCaptureExtension.swift:17,AVCamCaptureExtension/AVCamCaptureExtension.swift:25). - The launch work splits into two layers that never call each other directly. The capture layer (
CaptureService.setUpSession()) opts outputs into deferred start. The view layer gates controls oncamera.status == .running. The only link between them isCameraModel.status. - The deferral is implemented once, as a
Viewextension (AVCam/Support/ViewExtensions.swift:64), and applied by the views that should wait (AVCam/Views/Toolbars/MainToolbar/MainToolbar.swift:25,AVCam/Views/Controls/CaptureModeView.swift:44,AVCam/Views/Toolbars/FeatureToolbar/FeatureToolbar.swift:36). - The project still includes the AVCam rotation coordinator, which
setUpSession()creates after it adds the outputs (AVCam/CaptureService.swift:158).
Overall architecture
flowchart LR
App["AVCamApp .task"] --> Model["CameraModel (@MainActor, status)"]
Model --> Service["CaptureService actor (sessionQueue executor)"]
Service --> Session["AVCaptureSession (automaticallyRunsDeferredStart)"]
Service --> Outputs["Photo / movie outputs (isDeferredStartEnabled)"]
Service -. "nonisolated previewSource" .-> Preview["CameraPreview / PreviewView"]
Model --> Gate["hidden(until: status == .running)"]
Gate --> Controls["Thumbnail, switch camera, mode picker, feature toolbar"]
Model --> Shutter["CaptureButton (always visible)"]
Reference code
AVCam/CaptureService.swift:137 — deferred-start configuration in setUpSession()
private func setUpSession() throws {
// ...
do {
// ...
// Deferred start brings up the preview first, and prepares the capture outputs a moment
// later. An app that links the iOS 26 SDK gets this behavior by default; these
// assignments show the properties that control it.
captureSession.automaticallyRunsDeferredStart = true
// Add the photo capture output as the default output type.
photoCapture.output.isDeferredStartEnabled = photoCapture.output.isDeferredStartSupported
try addOutput(photoCapture.output)
// If the capture mode is set to `video`, add a movie capture output.
if captureMode == .video {
// Add the movie output as the default output type.
movieCapture.output.isDeferredStartEnabled = movieCapture.output.isDeferredStartSupported
try addOutput(movieCapture.output)
setHDRVideoEnabled(isHDRVideoEnabled)
}
// ...
} catch {
throw CameraError.setupFailed
}
}Interpretation
Observed: the sample uses the automatic mode. It sets automaticallyRunsDeferredStart = true and enables isDeferredStartEnabled on each output only when isDeferredStartSupported is true. The source does not call runDeferredStartWhenNeeded or set a deferred-start delegate, so there is no manual trigger to analyze. The source comment notes that these assignments match the default for an app that links the iOS 26 SDK. They make the opt-in visible rather than changing behavior.
Inference: the architecture needs very little extra structure for fast launch because AVCam already isolates session work in an actor with its own queue. The launch-specific edits add configuration flags in the actor and a visibility rule in the views.
Ownership and state
classDiagram
AVCamApp *-- CameraModel : @State camera
AVCamCaptureExtension *-- CameraModel : @State camera
CameraModel *-- CaptureService : private let
CameraModel *-- MediaLibrary : private let
CaptureService *-- AVCaptureSession : private let
CaptureService *-- PhotoCapture : private let
CaptureService *-- MovieCapture : private let
MainToolbar o-- CameraModel : reads status
PreviewContainer *-- CoverState : @State coverOpacity
CameraUI *-- WindowSize : @State windowSize
Ownership evidence
AVCam/CameraModel.swift:68 — the single writer of the launch gate
func start() async {
// Verify that the person authorizes the app to use device cameras and microphones.
guard await captureService.isAuthorized else {
status = .unauthorized
return
}
do {
// Synchronize the state of the model with the persistent state.
await syncState()
// Start the capture service to start the flow of data.
try await captureService.start(with: cameraState)
observeState()
status = .running
} catch {
logger.error("Failed to start capture service. \(error)")
status = .failed
}
}| Owner | Object or state | Relationship | Mutation authority |
|---|---|---|---|
AVCamApp |
CameraModel |
Creates it in @State private var camera (AVCam/AVCamApp.swift:16) |
CameraModel |
CameraModel |
status |
private(set) var status (AVCam/CameraModel.swift:24). It is set to .running only after captureService.start(with:) returns (AVCam/CameraModel.swift:80). |
CameraModel, on the main actor |
CameraModel |
CaptureService, MediaLibrary |
Creates and retains them in private let (AVCam/CameraModel.swift:54, AVCam/CameraModel.swift:57) |
Each actor changes its own state |
CaptureService |
AVCaptureSession, PhotoCapture, MovieCapture |
Creates them in private let (AVCam/CaptureService.swift:31, AVCam/CaptureService.swift:34) |
Only the actor |
CaptureService |
Deferred-start flags on the session and outputs | Assigned once in setUpSession() (AVCam/CaptureService.swift:140) |
Only the actor |
| Control views | Visibility | Read camera.status and apply hidden(until:). They store no launch state of their own. (AVCam/Views/Toolbars/MainToolbar/MainToolbar.swift:35) |
None; derived from model state |
PreviewContainer |
coverOpacity |
@State private var (AVCam/Views/PreviewContainer.swift:31), changed in updateCoverOpacity |
PreviewContainer |
CameraUI |
windowSize |
@State private var (AVCam/Views/CameraUI.swift:21), written by onGeometryChange |
CameraUI |
Observed: launch readiness is not a separate flag. Views reuse the existing CameraStatus value, and StatusOverlayView already renders the unauthorized, failed, and interrupted cases (AVCam/Views/Overlays/StatusOverlayView.swift:14). Controls stay at zero opacity in every state except .running.
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 |
|---|---|---|---|
| Start-up task | .task { await camera.start() } |
SwiftUI creates the start task and ties it to the root view’s lifetime. The method it awaits, CameraModel.start(), is main-actor isolated. |
AVCam/AVCamApp.swift:25 |
| Main-actor isolation | @MainActor final class CameraModel |
status, and the decision to set it to .running, are main-actor state |
AVCam/CameraModel.swift:19 |
| Actor executor | actor CaptureService with unownedExecutor returning sessionQueue.asUnownedSerialExecutor() |
start(with:), setUpSession(), and captureSession.startRunning() run as jobs on the serial queue sessionQueue |
AVCam/CaptureService.swift:68, AVCam/CaptureService.swift:71, AVCam/CaptureService.swift:108 |
| Ordered launch steps | Sequential awaits in CameraModel.start() |
Authorization, then syncState() (which awaits CameraState.current), then captureService.start(with:), then observeState(), then status = .running |
AVCam/CameraModel.swift:70, AVCam/CameraModel.swift:76, AVCam/CameraModel.swift:78 |
| Preview attachment | nonisolated func setSession(_:) creating Task { @MainActor in previewLayer.session = session } |
The preview layer is attached on the main actor, independently of the actor’s setup job | AVCam/Views/CameraPreview.swift:63, AVCam/Views/CameraPreview.swift:66 |
Plain Task {} inheriting the main actor |
Four for await loops in observeState() |
Started after the session runs. The loops are not stored, so nothing cancels them. | AVCam/CameraModel.swift:198 |
Plain Task {} inheriting the actor |
Notification loops in observeNotifications(); monitorSystemPreferredCamera() |
Each body mutates isolated state without await, so it inherits CaptureService isolation. They are unretained. |
AVCam/CaptureService.swift:359, AVCam/CaptureService.swift:557 |
| Retained, cancellable task | subjectAreaChangeTask?.cancel(), then a new Task |
The only stored actor task | AVCam/CaptureService.swift:443, AVCam/CaptureService.swift:452 |
| Caller-isolation inheritance | monitorProgress(of:isolation: isolated (any Actor)? = #isolation) |
The photo-progress task captures the calling actor’s isolation | AVCam/Capture/PhotoCapture.swift:89 |
| Shutter tap | Task { await camera.capturePhoto() } in CaptureButton |
Created from a main-actor view action. The button is never hidden, so a tap can arrive before deferred start finishes. | AVCam/Views/Toolbars/MainToolbar/CaptureButton.swift:37 |
| Main run-loop timer | Timer.publish(every:on: .main, in: .common) |
Recording-duration ticks. The stored cancellable is cancelled on stop. | AVCam/Capture/MovieCapture.swift:84, AVCam/Capture/MovieCapture.swift:96 |
| Compiler checking level | SWIFT_STRICT_CONCURRENCY = minimal, @preconcurrency import AVFoundation |
Concurrency checking is minimal, so the compiler does not fully verify these crossings | AVCam.xcodeproj/project.pbxproj:949, AVCam/CaptureService.swift:9 |
Reference code
AVCam/CaptureService.swift:99 — session start on the actor’s queue
func start(with state: CameraState) async throws {
// Set initial operating state.
captureMode = state.captureMode
isHDRVideoEnabled = state.isVideoHDREnabled
// Exit early if not authorized or the session is already running.
guard await isAuthorized, !captureSession.isRunning else { return }
// Configure the session and start it.
try setUpSession()
captureSession.startRunning()
}Interpretation: the README explains that configuring and starting a session are blocking operations, and that the actor keeps them off the main thread (README.md:13). The custom executor (AVCam/CaptureService.swift:71) is the code that makes this true. While startRunning() blocks sessionQueue, CameraModel.start() is suspended at await, so the main actor can keep rendering the preview container and the visible shutter. Deferred start then shortens the blocking call itself by postponing output preparation (per the source comment at AVCam/CaptureService.swift:137).
Not established by source: how long deferred start takes, or exactly when outputs become ready. The app has no deferred-start callback and does not observe readiness. Its only signal is CameraModel.status, which becomes .running once startRunning() returns.
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 |
|---|---|---|---|
| Launch gate | @Observable CameraModel.status read by hidden(until: camera.status == .running) |
Controls fade in over 0.3 s once status becomes .running |
AVCam/CameraModel.swift:20, AVCam/Support/ViewExtensions.swift:65 |
| Scene lifecycle | @Environment(\.scenePhase) with .onChange(of: scenePhase, updateCoverOpacity) |
Covers the preview in .background and fades the cover out in .active |
AVCam/Views/PreviewContainer.swift:27, AVCam/Views/PreviewContainer.swift:67, AVCam/Views/PreviewContainer.swift:81 |
| Layout measurement | onGeometryChange(for: CGSize.self) into windowSize |
bottomPadding uses the measured window instead of a screen-wide constant |
AVCam/Views/CameraUI.swift:33, AVCam/Views/CameraUI.swift:89 |
| Actor-to-model state | @Published private(set) on the actor, read through .values |
Capture activity, capabilities, and the fullscreen-controls flag reach the model | AVCam/CaptureService.swift:17, AVCam/CameraModel.swift:207 |
| Output-to-actor state | Publishers.Merge(...).assign(to: &$captureActivity) |
Combines photo and movie activity | AVCam/CaptureService.swift:540 |
| Framework import | SwiftUI, Combine only, in CameraModel.swift |
The model file does not import AVFoundation | AVCam/CameraModel.swift:8, AVCam/CameraModel.swift:9 |
| Framework import | @preconcurrency import AVFoundation |
Capture and preview code | AVCam/CaptureService.swift:9, AVCam/Views/CameraPreview.swift:9 |
| Framework import | ExtensionKit, LockedCameraCapture |
Capture-extension entry point | AVCamCaptureExtension/AVCamCaptureExtension.swift:8, AVCamCaptureExtension/AVCamCaptureExtension.swift:10 |
| Package declaration | None | The project file declares no Swift package dependency | AVCam.xcodeproj/project.pbxproj:445 |
Combine scheduling: the source never calls receive(on:) or subscribe(on:). Delivery to the model goes through .values inside main-actor tasks.
Class and protocol design
classDiagram
class Camera {
<<protocol>>
status
}
class OutputService {
<<protocol>>
updateConfiguration(device)
}
class PreviewSource {
<<protocol>>
}
CameraModel ..|> Camera
PreviewCameraModel ..|> Camera
PhotoCapture ..|> OutputService
MovieCapture ..|> OutputService
DefaultPreviewSource ..|> PreviewSource
MainToolbar --> Camera : generic, reads status
CaptureService --> OutputService : updateConfiguration
Reference code
AVCam/Capture/MovieCapture.swift:100 — connection setup moved out of the record path
func updateConfiguration(for device: AVCaptureDevice) {
// The app supports HDR video capture if the active format supports it.
isHDRSupported = device.activeFormat10BitVariant != nil
// The output has a video connection only while the session contains it, which is the
// case in video capture mode. Adding the output creates a new connection, so the service
// configures it here rather than when recording starts.
guard let connection = movieOutput.connection(with: .video) else { return }
// Configure the connection for HEVC capture.
if movieOutput.availableVideoCodecTypes.contains(.hevc) {
movieOutput.setOutputSettings([AVVideoCodecKey: AVVideoCodecType.hevc], for: connection)
}
// ...
if connection.isVideoStabilizationSupported {
connection.preferredVideoStabilizationMode = .auto
}
}| Type | Responsibility | Depends on |
|---|---|---|
CaptureService (actor) |
Session setup with deferred start, device and mode changes, output configuration fan-out | AVCaptureSession, OutputService conformers |
CameraModel (@MainActor class) |
Runs the ordered launch sequence and publishes status |
CaptureService, MediaLibrary, CameraState |
Camera (protocol) |
View-facing contract that includes status |
Value types in DataTypes.swift |
PhotoCapture |
Photo output; enables responsive capture, fast capture prioritization, and auto-deferred delivery when supported | OutputService |
MovieCapture |
Movie output; prepares the video connection in updateConfiguration |
OutputService |
View.hidden(until:) |
One reusable launch-visibility rule | SwiftUI opacity, animation |
Observed: OutputService.updateConfiguration(for:) is the hook CaptureService calls for every output whenever the configuration changes (AVCam/CaptureService.swift:527). This tree uses that hook to move HEVC and stabilization setup ahead of recording. The source comment gives the reason: it avoids “changing the framing at the moment recording begins” (AVCam/Capture/MovieCapture.swift:116).
Access control
| Symbol | Access | Verified effect | Likely rationale |
|---|---|---|---|
CameraModel.status (AVCam/CameraModel.swift:24) |
private(set) |
Views can read the launch gate but cannot set it | Inference: only the start sequence can declare the camera running. |
CameraModel.captureService (AVCam/CameraModel.swift:57) |
private |
Views cannot call the actor or toggle deferred start | Inference: launch tuning stays in the capture layer. |
CaptureService.setUpSession() (AVCam/CaptureService.swift:113) |
private |
Deferred-start configuration is reachable only through start(with:) |
Inference: the flags are applied once, before startRunning(). |
CaptureService.previewSource (AVCam/CaptureService.swift:28) |
implicit internal, nonisolated let |
Readable synchronously from main-actor views | Inference: the preview can attach without waiting for an actor hop. |
PreviewContainer.coverOpacity, CameraUI.windowSize (AVCam/Views/PreviewContainer.swift:31, AVCam/Views/CameraUI.swift:21) |
@State private |
View-local presentation state | Inference: launch and resume polish is view-owned and kept out of the model. |
View.hidden(until:) (AVCam/Support/ViewExtensions.swift:64) |
implicit internal |
Available to any view in the module | Inference: one shared modifier instead of a copied if in each control. |
Reference code
AVCam/Support/ViewExtensions.swift:59 — the shared deferral modifier
extension View {
/// Hides this view until the capture session is running.
///
/// The app applies this to its controls so that it doesn't show UI until it's
/// functional, and so the preview is the first thing to appear at launch.
func hidden(until isRunning: Bool) -> some View {
opacity(isRunning ? 1 : 0)
.animation(.easeIn(duration: 0.3), value: isRunning)
}
}Observed: the modifier uses opacity rather than conditional inclusion, so the hidden controls stay in the view hierarchy and keep their layout space before launch completes. Most declarations use Swift’s implicit internal. The private and private(set) modifiers above are the meaningful boundaries.
Logic ownership and placement
| Logic | Owning type or file | Placement rationale |
|---|---|---|
| Opt outputs into deferred start | CaptureService.setUpSession() |
Runs on the actor’s queue, before any output is added (AVCam/CaptureService.swift:143). |
| Decide when the camera is running | CameraModel.start() |
The only writer of status, after the actor returns (AVCam/CameraModel.swift:80). |
| Hide noncritical controls | Each control view, through hidden(until:) |
Thumbnail, camera switch, mode picker, and feature toolbar (AVCam/Views/Toolbars/MainToolbar/MainToolbar.swift:25). |
| Keep the shutter available | MainToolbar |
CaptureButton gets no gate. The source comment relies on responsive capture to honor early taps (AVCam/Views/Toolbars/MainToolbar/MainToolbar.swift:27). That capability is enabled in PhotoCapture.updateConfiguration (AVCam/Capture/PhotoCapture.swift:124). |
| Avoid a stale frame on resume | PreviewContainer.updateCoverOpacity |
A view concern keyed to scenePhase (AVCam/Views/PreviewContainer.swift:81). |
| Prepare recording ahead of time | MovieCapture.updateConfiguration |
Configuration-time hook instead of the record path (AVCam/Capture/MovieCapture.swift:107). |
Design patterns
| Pattern | Source evidence | Purpose or tradeoff |
|---|---|---|
| Actor isolation | actor CaptureService with a sessionQueue executor (AVCam/CaptureService.swift:14, AVCam/CaptureService.swift:71); actor MediaLibrary (AVCam/Model/MediaLibrary.swift:13) |
Keeps the blocking session setup and start off the main actor, which deferred start depends on for a responsive launch |
| Protocol-oriented abstraction | Camera (AVCam/Model/Camera.swift:15) with CameraModel and PreviewCameraModel; OutputService (AVCam/Model/DataTypes.swift:143); PreviewSource (AVCam/Views/CameraPreview.swift:79) |
Views depend on Camera.status, not on capture types. Output preparation fans out through one protocol hook. |
| Delegate or data-source callbacks | PhotoCaptureDelegate, MovieCaptureDelegate, CaptureControlsDelegate (AVCam/CaptureService.swift:582) |
AVFoundation callbacks are bridged into continuations, streams, or @Published values |
| Publisher-backed observable state | @Published private(set) on the actor and output wrappers, consumed through .values (AVCam/CameraModel.swift:207) |
The capture layer publishes, and the main-actor model iterates. Only the owner writes. |
Removed from the draft: “Service object”, which came from the Service suffix and adds nothing beyond the verified actor and protocol boundaries, and “SwiftUI environment injection”. The views read system environment values (scenePhase, size classes), but the app does not inject its own dependencies through the environment: CameraModel is passed as an initializer argument (AVCam/AVCamApp.swift:23). No MVVM or Coordinator label is claimed.
Main application flow
sequenceDiagram
participant View as CameraView .task
participant Model as CameraModel (MainActor)
participant Service as CaptureService (sessionQueue)
participant Session as AVCaptureSession
participant Controls as Gated controls
View->>Model: await start()
Model->>Service: await isAuthorized
Model->>Model: await syncState()
Model->>Service: await start(with:)
Service->>Session: deferred-start flags, add outputs
Service->>Session: startRunning()
Service-->>Model: return
Model->>Model: observeState(); status = .running
Model-->>Controls: hidden(until:) fades in
Reference code
AVCam/Views/Toolbars/MainToolbar/MainToolbar.swift:19 — gated controls around an ungated shutter
var body: some View {
HStack {
ThumbnailButton(camera: camera)
// Hide the thumbnail button when a person interacts with capture controls.
.opacity(camera.prefersMinimizedUI ? 0 : 1)
// Hide it until the capture session runs, using a custom modifier.
.hidden(until: camera.status == .running)
Spacer()
// The capture button stays visible. A capture that arrives before deferred start
// finishes is still honored, because the photo output enables responsive capture.
CaptureButton(camera: camera)
Spacer()
SwitchCameraButton(camera: camera)
// Hide the camera selection when a person interacts with capture controls.
.opacity(camera.prefersMinimizedUI ? 0 : 1)
// Hide it until the capture session runs, using a custom modifier.
.hidden(until: camera.status == .running)
}
// ...
}Observed: the order in the diagram comes from CameraModel.start() and CaptureService.start(with:), which are excerpted in full under Ownership evidence and in the concurrency section’s reference code. Inference: since syncState() awaits the capture intent’s stored context before the session starts, that fetch is on the launch path. A team optimizing launch further could measure it, but the source makes no claim about its cost.
Naming conventions
- Types: the same AVCam role nouns as the base sample:
CaptureService(actor),CameraModel,PhotoCapture/MovieCaptureoutput wrappers, AVFoundation adopters ending inDelegate,PreviewContainer,CameraUI, toolbar and button views. - Protocols:
Camerafor the view-facing capability.OutputService,PreviewSource, andPreviewTargetfor collaborator roles. - Methods: the launch addition reads as a sentence at the call site:
.hidden(until: camera.status == .running). Lifecycle verbs arestart,setUpSession,syncState,observeState, andupdateConfiguration(for:). View handlers followupdate<Thing>(_:_:), matching theonChangesignature (updateBlurRadius,updateCoverOpacity). The AVFoundation flags use framework names (isDeferredStartEnabled,automaticallyRunsDeferredStart). App Booleans useis…/prefers…/should…. - Files and extensions: one primary type per file. The generic modifier is in
Support/ViewExtensions.swift, not in a launch-specific file, so launch behavior appears as edits to existing types rather than a new module.
Architecture takeaways
- Fast launch here is two independent mechanisms joined by one value. The capture actor defers output preparation, the views defer noncritical controls, and
CameraModel.statusis the only signal between them. - The actor’s custom serial executor is what keeps the blocking
startRunning()off the main actor (AVCam/CaptureService.swift:71). Deferred start shortens that call but does not remove the need for the actor. - Hide with opacity, not conditional view creation, when layout must stay stable. Keep the primary action (the shutter) available and rely on a framework capability (responsive capture) to accept early input.
- Move expensive or framing-changing setup from the action path to the configuration path.
MovieCapture.updateConfigurationprepares the video connection before the person presses record. - Task lifetimes are mostly implicit. The start-up task belongs to SwiftUI’s
.task, the observation loops are unretained, and onlysubjectAreaChangeTaskis stored and cancelled.
Source map
| Source file | Relevant symbols |
|---|---|
AVCam/CaptureService.swift |
CaptureService, sessionQueue, unownedExecutor, start(with:), setUpSession(), automaticallyRunsDeferredStart, isDeferredStartEnabled, CaptureControlsDelegate |
AVCam/CameraModel.swift |
CameraModel, start(), status, observeState() |
AVCam/Support/ViewExtensions.swift |
View.hidden(until:) |
AVCam/Views/Toolbars/MainToolbar/MainToolbar.swift |
MainToolbar |
AVCam/Views/Toolbars/MainToolbar/CaptureButton.swift |
CaptureButton |
AVCam/Views/Controls/CaptureModeView.swift |
CaptureModeView |
AVCam/Views/Toolbars/FeatureToolbar/FeatureToolbar.swift |
FeaturesToolbar |
AVCam/Views/PreviewContainer.swift |
PreviewContainer, updateCoverOpacity(_:_:) |
AVCam/Views/CameraUI.swift |
CameraUI, windowSize, bottomPadding |
AVCam/Views/CameraPreview.swift |
PreviewView.setSession(_:), PreviewSource |
AVCam/Views/Overlays/StatusOverlayView.swift |
StatusOverlayView |
AVCam/Capture/PhotoCapture.swift |
PhotoCapture, updateConfiguration(for:), monitorProgress(of:isolation:) |
AVCam/Capture/MovieCapture.swift |
MovieCapture, updateConfiguration(for:) |
AVCam/Model/Camera.swift |
Camera |
AVCam/Model/DataTypes.swift |
OutputService, CameraStatus |
AVCam/Model/MediaLibrary.swift |
MediaLibrary |
AVCam/AVCamApp.swift |
AVCamApp |
AVCamCaptureExtension/AVCamCaptureExtension.swift |
AVCamCaptureExtension |
AVCam.xcodeproj/project.pbxproj |
Targets, Sources phases, SWIFT_STRICT_CONCURRENCY, SUPPORTS_MACCATALYST |
README.md |
Generic AVCam overview and actor rationale |