Sample CodeiOS 18.0, iPadOS 18.0, Mac Catalyst 18.0, Xcode 16.0Reviewed 2026-07-21View on Apple Developer

Capturing consistent color images

At a glance

Item Summary
Purpose Capture Constant Color photos, optional fallback frames, confidence maps, and normal photos, then show/save the results.
App architecture SwiftUI view owns an observable DataModel; the model owns a concrete Camera, which owns the capture session, outputs, delegates, and result state.
Main patterns MVVM-style preview bridge, async stream, AVFoundation delegates, queue-confined session object.
Project style Flat SwiftUI target with small view files, one view model, one large camera service, and an authorization helper.

Project structure

ConstantColorCam/
├── ConstantColorCamApp.swift
├── ContentView.swift
├── CameraView.swift
├── DataModel.swift
├── Camera.swift
├── ViewFinderView.swift
├── PhotosTabView.swift
├── PhotoLibrary.swift
└── Assets.xcassets/

Structure observations

  • View composition is split into focused SwiftUI files; capture and result mechanics are concentrated in Camera.swift.
  • DataModel has one job: convert the camera’s CIImage preview stream to SwiftUI Image state.
  • There is no app-defined protocol between DataModel and Camera; the relationship is concrete.

Overall architecture

Reference code

ConstantColorCam/DataModel.swift:12 — preview bridge

var camera = Camera()
var viewfinderImage: Image?

func handleCameraPreviews() async {
    let imageStream = camera.previewStream.map { $0.image }
    for await image in imageStream {
        Task { @MainActor in
            viewfinderImage = image
        }
    }
}

Interpretation

The observable model bridges the camera’s framework-oriented preview stream into SwiftUI image state. Camera setup, capture settings, photo classification, and saving all remain inside the concrete Camera object. Calling DataModel a view model is directly supported by its file comment; the broader architecture is MVVM-style rather than a fully abstracted MVVM stack.

Ownership and state

Ownership evidence

ConstantColorCam/CameraView.swift:11 and ConstantColorCam/DataModel.swift:10 — root model ownership

struct CameraView: View {
    @State private var model = DataModel()
}

@Observable
final class DataModel {
    var camera = Camera()
}
Owner Object or state Relationship Mutation authority
CameraView DataModel Creates and retains with @State SwiftUI retains model; model updates preview image.
DataModel Camera and viewfinderImage Creates the camera and consumes its stream DataModel writes preview state; camera’s app-internal properties remain technically mutable by module code.
Camera Session, output objects, queue, preview continuation Creates and retains capture resources Camera, normally on its session/output queues.
Camera Constant/fallback/normal photo and confidence results Assigns in photo delegate callback Setters are implicit internal, so the source does not enforce camera-only mutation.

Class and protocol design

The sample defines no app-specific protocol for capture or preview. Camera directly conforms to AVFoundation delegate protocols in extensions; PhotoLibrary is a stateless authorization helper with a static method.

Type Responsibility Depends on
DataModel Convert streamed CIImage values to SwiftUI Image state Camera, Core Image, SwiftUI
Camera Own capture graph, preview stream, capture settings, result processing, and saving AVFoundation, Core Image, PhotoKit
CameraView Own screen state, validate option combinations, send capture intent DataModel/Camera
ViewFinderView Display current preview image SwiftUI binding
PhotosTabView Present classified captured results SwiftUI, UIKit images
PhotoLibrary Check Photos authorization PhotoKit

Access control

Symbol Access Verified effect Likely rationale
Camera.captureSession, inputs/outputs, queue, device lookup, helpers private Other files cannot directly reconfigure capture internals Keeps session mechanics within Camera.
CameraView.model and local UI toggles private Screen-owned state cannot be accessed by other files Encapsulates transient presentation state.
CIImage.image extension fileprivate Conversion helper is usable only within DataModel.swift Avoids making a one-consumer conversion a module-wide API.
Camera settings and captured-result properties implicit internal, mutable Views in the module can read and assign them Simple sample binding/coordination; weaker invariant enforcement than private(set).
App types implicit internal Not exported outside the target No public framework surface is intended.

Reference code

ConstantColorCam/DataModel.swift:33 — same-file conversion helper

fileprivate extension CIImage {
    var image: Image? {
        let ciContext = CIContext()
        guard let cgImage = ciContext.createCGImage(self, from: self.extent) else { return nil }
        return Image(decorative: cgImage, scale: 1, orientation: .up)
    }
}

No public, open, or private(set) declaration appears in this sample’s Swift source.

Logic ownership and placement

Logic Owning type or file Placement rationale
Preview conversion/state DataModel The model bridges capture data to SwiftUI.
Option validation before capture CameraView.takePhoto() Produces immediate presentation alerts before sending intent.
Session/device/output configuration Camera Concrete owner of the AVFoundation graph.
Constant Color settings creation Camera.takePhoto() Requires output capabilities and current camera options.
Result classification/confidence extraction Camera photo-delegate extension Receives AVCapturePhoto and writes captured-result state.
Result presentation PhotosTabView Pure SwiftUI rendering of image values.

Design patterns

Pattern Source evidence Purpose or tradeoff
MVVM-style preview adapter ConstantColorCam/DataModel.swift:4 and handleCameraPreviews() at line 21 Converts framework buffers into observable view state.
Async stream ConstantColorCam/Camera.swift:143 and delegate yield at line 498 Decouples realtime preview production from SwiftUI consumption.
Delegate extensions ConstantColorCam/Camera.swift:456, :464, and :498 Organizes readiness, photo, and video callbacks by AVFoundation protocol.
Session queue confinement ConstantColorCam/Camera.swift:33 and setup/start calls Moves configuration and running changes off the main thread.
Concrete service object ConstantColorCam/Camera.swift:15 Keeps sample flow visible, but combines capture, result state, and persistence.

Main application flow

Reference code

ConstantColorCam/Camera.swift:389 — capture settings handoff

func takePhoto() {
    guard let photoOutput else { return }
    let photoSettings = AVCapturePhotoSettings()
    sessionQueue.async {
        photoSettings.flashMode = self.flashEnabled ? .on : .off
        photoSettings.isConstantColorEnabled = self.constantColorEnabled
        photoSettings.isConstantColorFallbackPhotoDeliveryEnabled = self.fallBackPhotoDeliveryEnabled
        photoOutput.capturePhoto(with: photoSettings, delegate: self)
    }
}

Naming conventions

  • Types: concise responsibility nouns (DataModel, Camera, PhotoLibrary) and SwiftUI role suffixes (CameraView, PhotosTabView).
  • Methods: command names (takePhoto, switchCaptureDevice) and event/bridge names (handleCameraPreviews).
  • State: Boolean properties use is..., ...Enabled, ...Supported, ...Visible, or ...Available.
  • Files: generally one primary type or closely related SwiftUI subviews per file.
  • There are no app-defined protocol naming conventions to assess.

Architecture takeaways

  • An AsyncStream is a compact bridge from capture callbacks to an observable SwiftUI model.
  • DataModel remains narrow, but Camera intentionally combines session, result, and save logic for sample readability.
  • fileprivate limits the one-off Core Image conversion to its sole source file.
  • Unlike AVCam, captured-result and setting properties use mutable internal access; ownership is conventional rather than compiler-enforced.
  • UI option validation lives in the screen, while AVFoundation capability/configuration checks stay in the camera object.

Source map

Source file Relevant symbols
ConstantColorCam/ConstantColorCamApp.swift App entry
ConstantColorCam/CameraView.swift Model owner, options, capture intent
ConstantColorCam/DataModel.swift Observable preview view model and conversion helper
ConstantColorCam/Camera.swift Capture graph, streams, delegates, results, save
ConstantColorCam/ViewFinderView.swift Preview rendering
ConstantColorCam/PhotosTabView.swift Result rendering
ConstantColorCam/PhotoLibrary.swift Authorization helper