axiom-camera-capture

AVCaptureSession, camera preview, photo capture, video recording, RotationCoordinator, session interruptions, deferred processing, capture responsiveness, zero-shutter-lag, photoQualityPrioritization, front camera mirroring

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" with this command: npx skills add megastep/codex-skills/megastep-codex-skills-axiom-camera-capture

Camera Capture with AVFoundation

Guides you through implementing camera capture: session setup, photo capture, video recording, responsive capture UX, rotation handling, and session lifecycle management.

When to Use This Skill

Use when you need to:

  • ☑ Build a custom camera UI (not system picker)
  • ☑ Capture photos with quality/speed tradeoffs
  • ☑ Record video with audio
  • ☑ Handle device rotation correctly (RotationCoordinator)
  • ☑ Make capture feel responsive (zero-shutter-lag)
  • ☑ Handle session interruptions (phone calls, multitasking)
  • ☑ Switch between front/back cameras
  • ☑ Configure capture quality and resolution

Example Prompts

"How do I set up a camera preview in SwiftUI?" "My camera freezes when I get a phone call" "The photo preview is rotated wrong on front camera" "How do I make photo capture feel instant?" "Should I use deferred processing?" "My camera takes too long to capture" "How do I switch between front and back cameras?" "How do I record video with audio?"

Red Flags

Signs you're making this harder than it needs to be:

  • ❌ Calling startRunning() on main thread (blocks UI for seconds)
  • ❌ Using deprecated videoOrientation instead of RotationCoordinator (iOS 17+)
  • ❌ Not observing session interruptions (app freezes on phone call)
  • ❌ Creating new AVCaptureSession for each capture (expensive)
  • ❌ Using .photo preset for video (wrong format)
  • ❌ Ignoring photoQualityPrioritization (slow captures)
  • ❌ Not handling .notAuthorized permission state
  • ❌ Modifying session without beginConfiguration()/commitConfiguration()
  • ❌ Using UIImagePickerController for custom camera UI (limited control)

Mandatory First Steps

Before implementing any camera feature:

1. Choose Your Capture Mode

What do you need?

┌─ Just let user pick a photo?
│  └─ Don't use AVFoundation - use PHPicker or PhotosPicker
│     See: $axiom-photo-library
│
├─ Simple photo/video capture with system UI?
│  └─ UIImagePickerController (but limited customization)
│
├─ Custom camera UI with photo capture?
│  └─ AVCaptureSession + AVCapturePhotoOutput
│     → Continue with this skill
│
├─ Custom camera UI with video recording?
│  └─ AVCaptureSession + AVCaptureMovieFileOutput
│     → Continue with this skill
│
└─ Both photo and video in same session?
   └─ AVCaptureSession + both outputs
      → Continue with this skill

2. Request Camera Permission

import AVFoundation

func requestCameraAccess() async -> Bool {
    let status = AVCaptureDevice.authorizationStatus(for: .video)

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

Info.plist required:

<key>NSCameraUsageDescription</key>
<string>Take photos and videos</string>

For audio (video recording):

<key>NSMicrophoneUsageDescription</key>
<string>Record audio with video</string>

3. Understand Session Architecture

AVCaptureSession
    ├─ Inputs
    │   ├─ AVCaptureDeviceInput (camera)
    │   └─ AVCaptureDeviceInput (microphone, for video)
    │
    ├─ Outputs
    │   ├─ AVCapturePhotoOutput (photos)
    │   ├─ AVCaptureMovieFileOutput (video files)
    │   └─ AVCaptureVideoDataOutput (raw frames)
    │
    └─ Connections (automatic between compatible input/output)

Key rule: All session configuration happens on a dedicated serial queue, never main thread.

Core Patterns

Pattern 1: Basic Session Setup

Use case: Set up camera preview with photo capture capability.

import AVFoundation

class CameraManager: NSObject {
    let session = AVCaptureSession()
    let photoOutput = AVCapturePhotoOutput()

    // CRITICAL: Dedicated serial queue for session work
    private let sessionQueue = DispatchQueue(label: "camera.session")

    func setupSession() {
        sessionQueue.async { [self] in
            session.beginConfiguration()
            defer { session.commitConfiguration() }

            // 1. Set session preset
            session.sessionPreset = .photo

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

            // 3. Add photo output
            guard session.canAddOutput(photoOutput) else { return }
            session.addOutput(photoOutput)

            // 4. Configure photo output
            photoOutput.isHighResolutionCaptureEnabled = true
            photoOutput.maxPhotoQualityPrioritization = .quality
        }
    }

    func startSession() {
        sessionQueue.async { [self] in
            if !session.isRunning {
                session.startRunning()  // Blocking call - never on main thread!
            }
        }
    }

    func stopSession() {
        sessionQueue.async { [self] in
            if session.isRunning {
                session.stopRunning()
            }
        }
    }
}

Cost: 30 min implementation

Pattern 2: SwiftUI Camera Preview

Use case: Display camera preview in SwiftUI view.

import SwiftUI
import AVFoundation

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 }
    }
}

// Usage in SwiftUI
struct CameraView: View {
    @StateObject private var camera = CameraManager()

    var body: some View {
        CameraPreview(session: camera.session)
            .ignoresSafeArea()
            .onAppear { camera.startSession() }
            .onDisappear { camera.stopSession() }
    }
}

Cost: 20 min implementation

Pattern 3: Rotation Handling with RotationCoordinator (iOS 17+)

Use case: Keep preview and captured photos correctly oriented regardless of device rotation.

Why RotationCoordinator: Deprecated videoOrientation requires manual observation of device orientation. RotationCoordinator automatically tracks gravity and provides angles.

import AVFoundation

class CameraManager {
    private var rotationCoordinator: AVCaptureDevice.RotationCoordinator?
    private var rotationObservation: NSKeyValueObservation?

    func setupRotationCoordinator(device: AVCaptureDevice, previewLayer: AVCaptureVideoPreviewLayer) {
        // Create coordinator with device and preview layer
        rotationCoordinator = AVCaptureDevice.RotationCoordinator(
            device: device,
            previewLayer: previewLayer
        )

        // Observe preview rotation changes
        rotationObservation = rotationCoordinator?.observe(
            \.videoRotationAngleForHorizonLevelPreview,
            options: [.new]
        ) { [weak previewLayer] coordinator, _ in
            // Update preview layer rotation on main thread
            DispatchQueue.main.async {
                previewLayer?.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview
            }
        }

        // Set initial rotation
        previewLayer.connection?.videoRotationAngle = rotationCoordinator!.videoRotationAngleForHorizonLevelPreview
    }

    func captureRotationAngle() -> CGFloat {
        // Use this angle when capturing photos
        rotationCoordinator?.videoRotationAngleForHorizonLevelCapture ?? 0
    }
}

When capturing:

func capturePhoto() {
    let settings = AVCapturePhotoSettings()

    // Apply rotation angle from coordinator
    if let connection = photoOutput.connection(with: .video) {
        connection.videoRotationAngle = captureRotationAngle()
    }

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

Cost: 45 min implementation, prevents 2+ hours debugging rotation issues

Pattern 4: Responsive Capture Pipeline (iOS 17+)

Use case: Make photo capture feel instant with zero-shutter-lag, overlapping captures, and responsive button states.

iOS 17+ introduces four complementary APIs that work together for maximum responsiveness:

4a. Zero Shutter Lag

Uses a ring buffer of recent frames to "time travel" back to the exact moment you tapped the shutter. Enabled automatically for iOS 17+ apps.

// Check if supported for current format
if photoOutput.isZeroShutterLagSupported {
    // Enabled by default for apps linking iOS 17+
    // Opt out if causing issues:
    // photoOutput.isZeroShutterLagEnabled = false
}

Why it matters: Without ZSL, there's a delay between tap and frame capture. For action shots, the moment is already over.

Requirements: iPhone XS and newer. Does NOT apply to flash captures, manual exposure, bracketed captures, or constituent photo delivery.

4b. Responsive Capture (Overlapping Captures)

Allows a new capture to start while the previous one is still processing:

// Check support first
if photoOutput.isZeroShutterLagSupported {
    photoOutput.isZeroShutterLagEnabled = true  // Required for responsive capture

    if photoOutput.isResponsiveCaptureSupported {
        photoOutput.isResponsiveCaptureEnabled = true
    }
}

Tradeoff: Increases peak memory usage. If your app is memory-constrained, consider leaving disabled.

Requirements: A12 Bionic (iPhone XS) and newer.

4c. Fast Capture Prioritization

Automatically adapts quality when taking multiple photos rapidly (like burst mode):

if photoOutput.isFastCapturePrioritizationSupported {
    photoOutput.isFastCapturePrioritizationEnabled = true
    // When enabled, rapid captures use "balanced" quality instead of "quality"
    // to maintain consistent shot-to-shot time
}

When to enable: User-facing toggle ("Prioritize Faster Shooting" in Camera.app). Off by default because it reduces quality.

4d. Readiness Coordinator (Button State Management)

Critical for UX: Provides synchronous updates for shutter button state without async lag.

class CameraManager {
    private var readinessCoordinator: AVCapturePhotoOutputReadinessCoordinator!

    func setupReadinessCoordinator() {
        readinessCoordinator = AVCapturePhotoOutputReadinessCoordinator(photoOutput: photoOutput)
        readinessCoordinator.delegate = self
    }

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

        // Tell coordinator to track this capture BEFORE calling capturePhoto
        readinessCoordinator.startTrackingCaptureRequest(using: settings)

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

extension CameraManager: AVCapturePhotoOutputReadinessCoordinatorDelegate {
    func readinessCoordinator(_ coordinator: AVCapturePhotoOutputReadinessCoordinator,
                              captureReadinessDidChange captureReadiness: AVCapturePhotoOutput.CaptureReadiness) {
        DispatchQueue.main.async {
            switch captureReadiness {
            case .ready:
                self.shutterButton.isEnabled = true
                self.shutterButton.alpha = 1.0

            case .notReadyMomentarily:
                // Brief delay - disable to prevent double-tap
                self.shutterButton.isEnabled = false

            case .notReadyWaitingForCapture:
                // Flash is firing - dim button
                self.shutterButton.alpha = 0.5

            case .notReadyWaitingForProcessing:
                // Processing previous photo - show spinner
                self.showProcessingIndicator()

            case .sessionNotRunning:
                self.shutterButton.isEnabled = false

            @unknown default:
                break
            }
        }
    }
}

Why use Readiness Coordinator: Without it, you'd need to track capture state manually and users might spam the shutter button during processing.

Quality Prioritization (Baseline)

Still useful even without the new APIs:

func capturePhoto() {
    var settings = AVCapturePhotoSettings()

    // Speed vs Quality tradeoff
    // .speed     - Fastest capture, lower quality
    // .balanced  - Good default
    // .quality   - Best quality, may have delay
    settings.photoQualityPrioritization = .speed

    // For specific use cases:
    // - Social sharing: .speed (users expect instant)
    // - Document scanning: .quality (accuracy matters)
    // - General photography: .balanced

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

Deferred Processing (iOS 17+):

For maximum responsiveness, capture returns immediately with proxy image, full Deep Fusion processing happens in background:

// Check support and enable deferred processing
if photoOutput.isAutoDeferredPhotoDeliverySupported {
    photoOutput.isAutoDeferredPhotoDeliveryEnabled = true
}

Delegate callbacks with deferred processing:

// Called for BOTH regular photos AND deferred proxies
func photoOutput(_ output: AVCapturePhotoOutput,
                 didFinishProcessingPhoto photo: AVCapturePhoto,
                 error: Error?) {
    guard error == nil else { return }

    // Non-deferred photo - save directly
    if !photo.isRawPhoto, let data = photo.fileDataRepresentation() {
        savePhotoToLibrary(data)
    }
}

// Called ONLY for deferred proxies - save to PhotoKit for later processing
func photoOutput(_ output: AVCapturePhotoOutput,
                 didFinishCapturingDeferredPhotoProxy deferredPhotoProxy: AVCaptureDeferredPhotoProxy,
                 error: Error?) {
    guard error == nil else { return }

    // CRITICAL: Save proxy to library ASAP before app is backgrounded
    // App may be force-quit if memory pressure is high during backgrounding
    guard let proxyData = deferredPhotoProxy.fileDataRepresentation() else { return }

    Task {
        try await PHPhotoLibrary.shared().performChanges {
            let request = PHAssetCreationRequest.forAsset()
            // Use .photoProxy resource type - triggers deferred processing in Photos
            request.addResource(with: .photoProxy, data: proxyData, options: nil)
        }
    }
}

When final processing happens:

  • On-demand when image is requested from PhotoKit
  • Or automatically when device is idle (plugged in, not in use)

Fetching images with deferred processing awareness:

// Request with secondary degraded image for smoother UX
let options = PHImageRequestOptions()
options.allowSecondaryDegradedImage = true  // New in iOS 17

PHImageManager.default().requestImage(
    for: asset,
    targetSize: targetSize,
    contentMode: .aspectFill,
    options: options
) { image, info in
    let isDegraded = info?[PHImageResultIsDegradedKey] as? Bool ?? false

    if isDegraded {
        // First: Low quality (immediate)
        // Second: Medium quality (new - while processing)
        // Third callback will be final quality
        self.showTemporaryImage(image)
    } else {
        // Final quality - processing complete
        self.showFinalImage(image)
    }
}

Requirements: iPhone 11 Pro and newer. Not used for flash captures or formats that don't benefit from extended processing.

Important considerations:

  • Can't apply pixel buffer customizations (filters, metadata changes) to deferred photos
  • Use PhotoKit adjustments after processing for edits
  • Get proxy into library ASAP - limited time when backgrounded

Cost: 1 hour implementation, prevents "camera feels slow" complaints

Pattern 5: Session Interruption Handling

Use case: Handle phone calls, multitasking, system camera usage.

class CameraManager {
    private var interruptionObservers: [NSObjectProtocol] = []

    func setupInterruptionHandling() {
        // Session was interrupted
        let interruptedObserver = NotificationCenter.default.addObserver(
            forName: .AVCaptureSessionWasInterrupted,
            object: session,
            queue: .main
        ) { [weak self] notification in
            guard let reason = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? Int,
                  let interruptionReason = AVCaptureSession.InterruptionReason(rawValue: reason) else {
                return
            }

            switch interruptionReason {
            case .videoDeviceNotAvailableInBackground:
                // App went to background - normal, will resume
                self?.showPausedOverlay()

            case .audioDeviceInUseByAnotherClient:
                // Another app using audio
                self?.showInterruptedBanner("Audio in use by another app")

            case .videoDeviceInUseByAnotherClient:
                // Another app using camera
                self?.showInterruptedBanner("Camera in use by another app")

            case .videoDeviceNotAvailableWithMultipleForegroundApps:
                // Split View/Slide Over - camera not available
                self?.showInterruptedBanner("Camera unavailable in Split View")

            case .videoDeviceNotAvailableDueToSystemPressure:
                // Thermal state - reduce quality or stop
                self?.handleThermalPressure()

            @unknown default:
                self?.showInterruptedBanner("Camera interrupted")
            }
        }
        interruptionObservers.append(interruptedObserver)

        // Session interruption ended
        let endedObserver = NotificationCenter.default.addObserver(
            forName: .AVCaptureSessionInterruptionEnded,
            object: session,
            queue: .main
        ) { [weak self] _ in
            self?.hideInterruptedBanner()
            self?.hidePausedOverlay()
            // Session automatically resumes - no need to call startRunning()
        }
        interruptionObservers.append(endedObserver)
    }

    deinit {
        interruptionObservers.forEach { NotificationCenter.default.removeObserver($0) }
    }
}

Cost: 30 min implementation, prevents "camera freezes" bug reports

Pattern 6: Camera Switching (Front/Back)

Use case: Toggle between front and back cameras.

func switchCamera() {
    sessionQueue.async { [self] in
        guard let currentInput = session.inputs.first as? AVCaptureDeviceInput else {
            return
        }

        let currentPosition = currentInput.device.position
        let newPosition: AVCaptureDevice.Position = currentPosition == .back ? .front : .back

        guard let newDevice = AVCaptureDevice.default(
            .builtInWideAngleCamera,
            for: .video,
            position: newPosition
        ) else {
            return
        }

        session.beginConfiguration()
        defer { session.commitConfiguration() }

        // Remove old input
        session.removeInput(currentInput)

        // Add new input
        do {
            let newInput = try AVCaptureDeviceInput(device: newDevice)
            if session.canAddInput(newInput) {
                session.addInput(newInput)

                // Update rotation coordinator for new device
                if let previewLayer = previewLayer {
                    setupRotationCoordinator(device: newDevice, previewLayer: previewLayer)
                }
            } else {
                // Fallback: restore old input
                session.addInput(currentInput)
            }
        } catch {
            session.addInput(currentInput)
        }
    }
}

Front camera mirroring: Front camera preview is mirrored by default (matches user expectation). Captured photos are NOT mirrored (correct for sharing). This is intentional.

Cost: 20 min implementation

Pattern 7: Video Recording

Use case: Record video with audio to file.

class CameraManager: NSObject {
    let movieOutput = AVCaptureMovieFileOutput()
    private var currentRecordingURL: URL?

    func setupVideoRecording() {
        sessionQueue.async { [self] in
            session.beginConfiguration()
            defer { session.commitConfiguration() }

            // Set video preset
            session.sessionPreset = .high  // Or .hd1920x1080, .hd4K3840x2160

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

            // Add movie output
            if session.canAddOutput(movieOutput) {
                session.addOutput(movieOutput)
            }
        }
    }

    func startRecording() {
        guard !movieOutput.isRecording else { return }

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

        currentRecordingURL = outputURL

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

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

    func stopRecording() {
        guard movieOutput.isRecording else { return }
        movieOutput.stopRecording()
    }
}

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

        // Video saved to outputFileURL
        saveVideoToPhotoLibrary(outputFileURL)
    }
}

Cost: 45 min implementation

Anti-Patterns

Anti-Pattern 1: Session Work on Main Thread

Wrong:

func startCamera() {
    session.startRunning()  // Blocks UI for 1-3 seconds!
}

Right:

func startCamera() {
    sessionQueue.async { [self] in
        session.startRunning()
    }
}

Why it matters: startRunning() is blocking. On main thread, UI freezes.

Anti-Pattern 2: Using Deprecated videoOrientation

Wrong (pre-iOS 17):

// Manually tracking orientation
NotificationCenter.default.addObserver(
    forName: UIDevice.orientationDidChangeNotification,
    object: nil,
    queue: .main
) { _ in
    // Manual rotation logic...
}

Right (iOS 17+):

let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: preview)
// Automatically tracks gravity, provides angles

Why it matters: RotationCoordinator handles edge cases (face-up, face-down) that manual tracking misses.

Anti-Pattern 3: Ignoring Session Interruptions

Wrong:

// No interruption handling - camera freezes on phone call

Right:

NotificationCenter.default.addObserver(
    forName: .AVCaptureSessionWasInterrupted,
    object: session,
    queue: .main
) { notification in
    // Show UI feedback
}

Why it matters: Without handling, camera appears frozen when interrupted.

Anti-Pattern 4: Modifying Session Without Configuration Block

Wrong:

session.removeInput(oldInput)
session.addInput(newInput)  // May fail mid-stream

Right:

session.beginConfiguration()
session.removeInput(oldInput)
session.addInput(newInput)
session.commitConfiguration()  // Atomic change

Why it matters: Without configuration block, session may enter invalid state between calls.

Pressure Scenarios

Scenario 1: "Just Make the Camera Work by Friday"

Context: Product wants camera feature shipped. You're considering skipping interruption handling.

Pressure: "It works when I test it, let's ship."

Reality: First user who gets a phone call while using camera will see frozen UI. App Store review may catch this.

Correct action:

  1. Implement interruption handling (30 min)
  2. Test by calling your test device during camera use
  3. Verify UI shows appropriate feedback

Push-back template: "Camera captures work, but the app freezes if a phone call comes in. I need 30 minutes to handle interruptions properly and avoid 1-star reviews."

Scenario 2: "The Camera is Too Slow"

Context: QA reports photo capture feels sluggish. PM wants it "instant like the system camera."

Pressure: "Just make it faster somehow."

Reality: Default settings prioritize quality over speed. System camera uses deferred processing.

Correct action:

  1. Set photoQualityPrioritization = .speed for social/sharing use cases
  2. Consider deferred processing for maximum responsiveness
  3. Show capture animation immediately (before processing completes)

Push-back template: "We're currently optimizing for image quality. I can make capture feel instant by prioritizing speed and showing the preview immediately while processing continues in background. This is what the system Camera app does."

Scenario 3: "Why is the Front Camera Photo Mirrored?"

Context: Designer reports front camera photos look "wrong" - they're not mirrored like the preview.

Pressure: "The preview shows it one way, the photo should match."

Reality: Preview is mirrored (user expectation - like a mirror). Photo is NOT mirrored (correct for sharing - text reads correctly). This is intentional behavior matching system camera.

Correct action:

  1. Explain this is Apple's standard behavior
  2. If business requires mirrored photos (selfie apps), manually mirror in post-processing
  3. Never mirror the preview differently than expected

Push-back template: "This is intentional Apple behavior. The preview is mirrored like a mirror so users can frame themselves, but the captured photo is unmirrored so text reads correctly when shared. We can add optional mirroring in post-processing if our use case requires it."

Checklist

Before shipping camera features:

Session Setup:

  • ☑ All session work on dedicated serial queue
  • startRunning() never called on main thread
  • ☑ Session preset matches use case (.photo for photos, .high for video)
  • ☑ Configuration changes wrapped in beginConfiguration()/commitConfiguration()

Permissions:

  • ☑ Camera permission requested before session setup
  • NSCameraUsageDescription in Info.plist
  • NSMicrophoneUsageDescription if recording audio
  • ☑ Graceful handling of denied permission

Rotation:

  • ☑ RotationCoordinator used (not deprecated videoOrientation)
  • ☑ Preview layer rotation updated via observation
  • ☑ Capture rotation angle applied when taking photos
  • ☑ Tested in all orientations (portrait, landscape, face-up)

Responsiveness:

  • ☑ photoQualityPrioritization set appropriately for use case
  • ☑ Capture button shows immediate feedback
  • ☑ Deferred processing considered for maximum speed

Interruptions:

  • ☑ Session interruption observer registered
  • ☑ UI feedback shown when interrupted
  • ☑ Tested with incoming phone call
  • ☑ Tested in Split View (iPad)

Camera Switching:

  • ☑ Front/back switch updates rotation coordinator
  • ☑ Switch happens on session queue
  • ☑ Fallback if new camera unavailable

Video Recording (if applicable):

  • ☑ Microphone input added
  • ☑ Recording delegate handles completion
  • ☑ File cleanup for temporary recordings

Resources

WWDC: 2021-10247, 2023-10105

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

Skills: axiom-camera-capture-ref, axiom-camera-capture-diag, axiom-photo-library

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.

Coding

ads-competitor

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

ads-meta

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

blog-write

No summary provided by upstream source.

Repository SourceNeeds Review
Coding

blog-rewrite

No summary provided by upstream source.

Repository SourceNeeds Review