axiom-camera-capture-ref

Camera Capture API Reference

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "axiom-camera-capture-ref" with this command: npx skills add charleswiltgen/axiom/charleswiltgen-axiom-axiom-camera-capture-ref

Camera Capture API Reference

Quick Reference

// SESSION SETUP import AVFoundation

let session = AVCaptureSession() let sessionQueue = DispatchQueue(label: "camera.session")

sessionQueue.async { session.beginConfiguration() session.sessionPreset = .photo

guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
      let input = try? AVCaptureDeviceInput(device: camera),
      session.canAddInput(input) else { return }
session.addInput(input)

let photoOutput = AVCapturePhotoOutput()
if session.canAddOutput(photoOutput) {
    session.addOutput(photoOutput)
}

session.commitConfiguration()
session.startRunning()

}

// CAPTURE PHOTO var settings = AVCapturePhotoSettings() settings.photoQualityPrioritization = .balanced photoOutput.capturePhoto(with: settings, delegate: self)

// ROTATION (iOS 17+) let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: previewLayer) previewLayer.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview

AVCaptureSession

Central coordinator for capture data flow.

Session Presets

Preset Resolution Use Case

.photo

Optimal for photos Photo capture

.high

Highest device quality Video recording

.medium

VGA quality Preview, lower storage

.low

CIF quality Minimal storage

.hd1280x720

720p HD video

.hd1920x1080

1080p Full HD video

.hd4K3840x2160

4K Ultra HD video

.inputPriority

Use device format Custom configuration

Session Configuration

// Batch configuration (atomic) session.beginConfiguration() defer { session.commitConfiguration() }

// Check preset support if session.canSetSessionPreset(.hd4K3840x2160) { session.sessionPreset = .hd4K3840x2160 }

// Add input/output if session.canAddInput(input) { session.addInput(input) }

if session.canAddOutput(output) { session.addOutput(output) }

Session Lifecycle

// Start (ALWAYS on background queue) sessionQueue.async { session.startRunning() // Blocking call }

// Stop sessionQueue.async { session.stopRunning() }

// Check state session.isRunning // true/false session.isInterrupted // true during phone calls, etc.

Session Notifications

// Session started NotificationCenter.default.addObserver( forName: .AVCaptureSessionDidStartRunning, object: session, queue: .main) { _ in }

// Session stopped NotificationCenter.default.addObserver( forName: .AVCaptureSessionDidStopRunning, object: session, queue: .main) { _ in }

// Session interrupted (phone call, etc.) NotificationCenter.default.addObserver( forName: .AVCaptureSessionWasInterrupted, object: session, queue: .main) { notification in let reason = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? Int }

// Interruption ended NotificationCenter.default.addObserver( forName: .AVCaptureSessionInterruptionEnded, object: session, queue: .main) { _ in }

// Runtime error NotificationCenter.default.addObserver( forName: .AVCaptureSessionRuntimeError, object: session, queue: .main) { notification in let error = notification.userInfo?[AVCaptureSessionErrorKey] as? Error }

Interruption Reasons

Reason Value Cause

.videoDeviceNotAvailableInBackground

1 App went to background

.audioDeviceInUseByAnotherClient

2 Another app using audio

.videoDeviceInUseByAnotherClient

3 Another app using camera

.videoDeviceNotAvailableWithMultipleForegroundApps

4 Split View (iPad)

.videoDeviceNotAvailableDueToSystemPressure

5 Thermal throttling

AVCaptureDevice

Represents a physical capture device (camera, microphone).

Getting Devices

// Default back camera AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back)

// Default front camera AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front)

// Default microphone AVCaptureDevice.default(for: .audio)

// Discovery session for all cameras let discoverySession = AVCaptureDevice.DiscoverySession( deviceTypes: [.builtInWideAngleCamera, .builtInUltraWideCamera, .builtInTelephotoCamera], mediaType: .video, position: .unspecified ) let cameras = discoverySession.devices

Device Types

Type Description

.builtInWideAngleCamera

Standard camera (1x)

.builtInUltraWideCamera

Ultra-wide camera (0.5x)

.builtInTelephotoCamera

Telephoto camera (2x, 3x)

.builtInDualCamera

Wide + telephoto

.builtInDualWideCamera

Wide + ultra-wide

.builtInTripleCamera

Wide + ultra-wide + telephoto

.builtInTrueDepthCamera

Front TrueDepth (Face ID)

.builtInLiDARDepthCamera

LiDAR depth

Device Configuration

do { try device.lockForConfiguration() defer { device.unlockForConfiguration() }

// Focus
if device.isFocusModeSupported(.continuousAutoFocus) {
    device.focusMode = .continuousAutoFocus
}

// Exposure
if device.isExposureModeSupported(.continuousAutoExposure) {
    device.exposureMode = .continuousAutoExposure
}

// Torch (flashlight)
if device.hasTorch && device.isTorchModeSupported(.on) {
    device.torchMode = .on
}

// Zoom
device.videoZoomFactor = 2.0  // 2x zoom

} catch { print("Failed to configure device: (error)") }

Switching Cameras

// Switch between front and back during active session func switchCamera() { sessionQueue.async { [self] in session.beginConfiguration() defer { session.commitConfiguration() }

    // Remove current camera input
    if let currentInput = session.inputs.first(where: { ($0 as? AVCaptureDeviceInput)?.device.hasMediaType(.video) == true }) as? AVCaptureDeviceInput {
        session.removeInput(currentInput)

        // Get opposite camera
        let newPosition: AVCaptureDevice.Position = currentInput.device.position == .back ? .front : .back
        guard let newDevice = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: newPosition),
              let newInput = try? AVCaptureDeviceInput(device: newDevice) else { return }

        if session.canAddInput(newInput) {
            session.addInput(newInput)
        }
    }
}

}

Important: Always switch on the session queue, within beginConfiguration/commitConfiguration.

Authorization

// Check status let status = AVCaptureDevice.authorizationStatus(for: .video)

switch status { case .authorized: break case .notDetermined: await AVCaptureDevice.requestAccess(for: .video) case .denied, .restricted: // Show settings prompt @unknown default: break }

AVCaptureDevice.RotationCoordinator (iOS 17+)

Automatically tracks device orientation and provides rotation angles.

Setup

// Create with device and preview layer let coordinator = AVCaptureDevice.RotationCoordinator( device: captureDevice, previewLayer: previewLayer )

Properties

Property Type Description

videoRotationAngleForHorizonLevelPreview

CGFloat Rotation for preview layer

videoRotationAngleForHorizonLevelCapture

CGFloat Rotation for captured output

Observation

// KVO observation for preview updates let observation = coordinator.observe( .videoRotationAngleForHorizonLevelPreview, options: [.new] ) { [weak previewLayer] coordinator, _ in DispatchQueue.main.async { previewLayer?.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview } }

// Set initial value previewLayer.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview

Applying to Capture

func capturePhoto() { if let connection = photoOutput.connection(with: .video) { connection.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelCapture } photoOutput.capturePhoto(with: settings, delegate: self) }

AVCapturePhotoOutput

Output for capturing still photos.

Configuration

let photoOutput = AVCapturePhotoOutput()

// High resolution photoOutput.isHighResolutionCaptureEnabled = true

// Max quality prioritization photoOutput.maxPhotoQualityPrioritization = .quality

// Deferred processing (iOS 17+) photoOutput.isAutoDeferredPhotoDeliveryEnabled = true

// Live Photo photoOutput.isLivePhotoCaptureEnabled = true

// Depth photoOutput.isDepthDataDeliveryEnabled = true

// Portrait Effects Matte photoOutput.isPortraitEffectsMatteDeliveryEnabled = true

Supported Features

// Check support before enabling photoOutput.isHighResolutionCaptureEnabled && photoOutput.isHighResolutionCaptureSupported photoOutput.isLivePhotoCaptureSupported photoOutput.isDepthDataDeliverySupported photoOutput.isPortraitEffectsMatteDeliverySupported photoOutput.maxPhotoQualityPrioritization // .speed, .balanced, .quality

Responsive Capture APIs (iOS 17+)

// Zero Shutter Lag - uses ring buffer for instant capture photoOutput.isZeroShutterLagSupported photoOutput.isZeroShutterLagEnabled // true by default for iOS 17+ apps

// Responsive Capture - overlapping captures photoOutput.isResponsiveCaptureSupported photoOutput.isResponsiveCaptureEnabled

// Fast Capture Prioritization - adapts quality for burst-like capture photoOutput.isFastCapturePrioritizationSupported photoOutput.isFastCapturePrioritizationEnabled

// Deferred Processing - proxy + background processing photoOutput.isAutoDeferredPhotoDeliverySupported photoOutput.isAutoDeferredPhotoDeliveryEnabled

AVCapturePhotoOutputReadinessCoordinator (iOS 17+)

Provides synchronous shutter button state updates.

Setup

let coordinator = AVCapturePhotoOutputReadinessCoordinator(photoOutput: photoOutput) coordinator.delegate = self

Tracking Captures

// Call BEFORE capturePhoto() coordinator.startTrackingCaptureRequest(using: settings) photoOutput.capturePhoto(with: settings, delegate: self)

Delegate

func readinessCoordinator(_ coordinator: AVCapturePhotoOutputReadinessCoordinator, captureReadinessDidChange captureReadiness: AVCapturePhotoOutput.CaptureReadiness) { switch captureReadiness { case .ready: // Can capture immediately case .notReadyMomentarily: // Brief delay, prevent double-tap case .notReadyWaitingForCapture: // Flash firing, sensor reading case .notReadyWaitingForProcessing: // Processing previous photo case .sessionNotRunning: // Session stopped @unknown default: break } }

AVCapturePhotoSettings

Configuration for a single photo capture.

Basic Settings

// Standard JPEG var settings = AVCapturePhotoSettings()

// HEIF format settings = AVCapturePhotoSettings(format: [AVVideoCodecKey: AVVideoCodecType.hevc])

// RAW settings = AVCapturePhotoSettings(rawPixelFormatType: kCVPixelFormatType_14Bayer_BGGR)

// RAW + JPEG settings = AVCapturePhotoSettings( rawPixelFormatType: kCVPixelFormatType_14Bayer_BGGR, processedFormat: [AVVideoCodecKey: AVVideoCodecType.jpeg] )

Quality Prioritization

Value Speed Quality Use Case

.speed

Fastest Lower Social sharing, rapid capture

.balanced

Medium Good General photography

.quality

Slowest Best Professional, documents

settings.photoQualityPrioritization = .speed

Flash

settings.flashMode = .auto // .off, .on, .auto

Apple ProRAW and HDR

// Check ProRAW support if photoOutput.isAppleProRAWSupported { photoOutput.isAppleProRAWEnabled = true

// Capture ProRAW
let query = photoOutput.isAppleProRAWEnabled
    ? AVCapturePhotoOutput.AppleProRAWQuery(photoOutput)
    : nil
if let rawType = query?.availableRawPixelFormatTypes.first {
    let settings = AVCapturePhotoSettings(
        rawPixelFormatType: rawType,
        processedFormat: [AVVideoCodecKey: AVVideoCodecType.hevc]
    )
}

}

// HDR configuration settings.photoQualityPrioritization = .quality // Enables computational photography/HDR // HDR is automatic with .balanced or .quality — no separate toggle needed

Note: ProRAW requires iPhone 12 Pro or later. HDR is automatic with quality prioritization — Apple's Deep Fusion and Smart HDR are controlled by the system based on the quality setting.

Resolution

// High resolution still image settings.isHighResolutionPhotoEnabled = true

// Max dimensions (limit resolution) settings.maxPhotoDimensions = CMVideoDimensions(width: 4032, height: 3024)

Preview/Thumbnail

// Preview for immediate display settings.previewPhotoFormat = [ kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_32BGRA ]

// Thumbnail settings.embeddedThumbnailPhotoFormat = [ AVVideoCodecKey: AVVideoCodecType.jpeg, AVVideoWidthKey: 160, AVVideoHeightKey: 120 ]

Important Notes

// Settings cannot be reused // Each capture needs a NEW settings instance let settings1 = AVCapturePhotoSettings() // Use once let settings2 = AVCapturePhotoSettings() // Use for second capture

// Copy settings for similar captures let settings2 = AVCapturePhotoSettings(from: settings1)

AVCapturePhotoCaptureDelegate

Delegate for photo capture events.

extension CameraManager: AVCapturePhotoCaptureDelegate {

// Photo capture will begin
func photoOutput(_ output: AVCapturePhotoOutput,
                 willBeginCaptureFor resolvedSettings: AVCaptureResolvedPhotoSettings) {
    // Show shutter animation
}

// Photo capture finished
func photoOutput(_ output: AVCapturePhotoOutput,
                 didFinishProcessingPhoto photo: AVCapturePhoto,
                 error: Error?) {
    guard error == nil else {
        print("Capture error: \(error!)")
        return
    }

    // Get JPEG data
    if let data = photo.fileDataRepresentation() {
        savePhoto(data)
    }

    // Or get raw pixel buffer
    if let pixelBuffer = photo.pixelBuffer {
        processBuffer(pixelBuffer)
    }
}

// Deferred processing proxy (iOS 17+)
func photoOutput(_ output: AVCapturePhotoOutput,
                 didFinishCapturingDeferredPhotoProxy deferredPhotoProxy: AVCaptureDeferredPhotoProxy,
                 error: Error?) {
    guard error == nil, let data = deferredPhotoProxy.fileDataRepresentation() else { return }
    replaceThumbnailWithFinal(data)
}

}

AVCaptureMovieFileOutput

Output for recording video to file.

Setup

let movieOutput = AVCaptureMovieFileOutput()

if session.canAddOutput(movieOutput) { session.addOutput(movieOutput) }

// Add audio input if let microphone = AVCaptureDevice.default(for: .audio), let audioInput = try? AVCaptureDeviceInput(device: microphone), session.canAddInput(audioInput) { session.addInput(audioInput) }

Recording

// Start recording let outputURL = FileManager.default.temporaryDirectory .appendingPathComponent(UUID().uuidString) .appendingPathExtension("mov")

// Apply rotation if let connection = movieOutput.connection(with: .video) { connection.videoRotationAngle = rotationCoordinator.videoRotationAngleForHorizonLevelCapture }

movieOutput.startRecording(to: outputURL, recordingDelegate: self)

// Stop recording movieOutput.stopRecording()

// Check state movieOutput.isRecording movieOutput.recordedDuration movieOutput.recordedFileSize

Delegate

extension CameraManager: AVCaptureFileOutputRecordingDelegate {

func fileOutput(_ output: AVCaptureFileOutput,
                didStartRecordingTo fileURL: URL,
                from connections: [AVCaptureConnection]) {
    // Recording started
}

func fileOutput(_ output: AVCaptureFileOutput,
                didFinishRecordingTo outputFileURL: URL,
                from connections: [AVCaptureConnection],
                error: Error?) {
    if let error = error {
        print("Recording failed: \(error)")
        return
    }

    // Video saved to outputFileURL
    saveToPhotoLibrary(outputFileURL)
}

}

AVCaptureVideoPreviewLayer

Layer for displaying camera preview.

Setup

let previewLayer = AVCaptureVideoPreviewLayer(session: session) previewLayer.videoGravity = .resizeAspectFill previewLayer.frame = view.bounds view.layer.addSublayer(previewLayer)

Video Gravity

Value Behavior

.resizeAspect

Fit entire image, may letterbox

.resizeAspectFill

Fill layer, may crop edges

.resize

Stretch to fill (distorts)

SwiftUI Integration

struct CameraPreview: UIViewRepresentable { let session: AVCaptureSession

func makeUIView(context: Context) -> PreviewView {
    let view = PreviewView()
    view.previewLayer.session = session
    view.previewLayer.videoGravity = .resizeAspectFill
    return view
}

func updateUIView(_ uiView: PreviewView, context: Context) {}

class PreviewView: UIView {
    override class var layerClass: AnyClass { AVCaptureVideoPreviewLayer.self }
    var previewLayer: AVCaptureVideoPreviewLayer { layer as! AVCaptureVideoPreviewLayer }
}

}

Common Code Patterns

Complete Camera Manager

import AVFoundation

@MainActor class CameraManager: NSObject, ObservableObject { let session = AVCaptureSession() let photoOutput = AVCapturePhotoOutput() private let sessionQueue = DispatchQueue(label: "camera.session") private var rotationCoordinator: AVCaptureDevice.RotationCoordinator? private var rotationObservation: NSKeyValueObservation?

@Published var isSessionRunning = false

func setup() async -> Bool {
    guard await AVCaptureDevice.requestAccess(for: .video) else { return false }

    return await withCheckedContinuation { continuation in
        sessionQueue.async { [self] in
            session.beginConfiguration()
            defer { session.commitConfiguration() }

            session.sessionPreset = .photo

            guard let camera = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back),
                  let input = try? AVCaptureDeviceInput(device: camera),
                  session.canAddInput(input) else {
                continuation.resume(returning: false)
                return
            }
            session.addInput(input)

            guard session.canAddOutput(photoOutput) else {
                continuation.resume(returning: false)
                return
            }
            session.addOutput(photoOutput)
            photoOutput.maxPhotoQualityPrioritization = .quality

            continuation.resume(returning: true)
        }
    }
}

func start() {
    sessionQueue.async { [self] in
        session.startRunning()
        DispatchQueue.main.async {
            self.isSessionRunning = self.session.isRunning
        }
    }
}

func stop() {
    sessionQueue.async { [self] in
        session.stopRunning()
        DispatchQueue.main.async {
            self.isSessionRunning = false
        }
    }
}

func capturePhoto() {
    var settings = AVCapturePhotoSettings()
    settings.photoQualityPrioritization = .balanced

    if let connection = photoOutput.connection(with: .video),
       let angle = rotationCoordinator?.videoRotationAngleForHorizonLevelCapture {
        connection.videoRotationAngle = angle
    }

    photoOutput.capturePhoto(with: settings, delegate: self)
}

}

extension CameraManager: AVCapturePhotoCaptureDelegate { nonisolated func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { guard let data = photo.fileDataRepresentation() else { return } // Handle photo data } }

Resources

Docs: /avfoundation/avcapturesession, /avfoundation/avcapturedevice, /avfoundation/avcapturephotosettings, /avfoundation/avcapturedevice/rotationcoordinator

Skills: axiom-camera-capture, axiom-camera-capture-diag

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

axiom-vision

No summary provided by upstream source.

Repository SourceNeeds Review
General

axiom-swiftdata

No summary provided by upstream source.

Repository SourceNeeds Review
General

axiom-swiftui-26-ref

No summary provided by upstream source.

Repository SourceNeeds Review
General

axiom-swiftui-architecture

No summary provided by upstream source.

Repository SourceNeeds Review