axiom-camera-capture-diag

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Camera Capture Diagnostics

相机拍摄问题诊断

Systematic troubleshooting for AVFoundation camera issues: frozen preview, wrong rotation, slow capture, session interruptions, and permission problems.
针对AVFoundation相机问题的系统化排查方案:包括预览卡顿、旋转错误、拍摄缓慢、会话中断以及权限问题等。

Overview

概述

Core Principle: When camera doesn't work, the problem is usually:
  1. Threading (session work on main thread) - 35%
  2. Session lifecycle (not started, interrupted, not configured) - 25%
  3. Rotation (deprecated APIs, missing coordinator) - 20%
  4. Permissions (denied, not requested) - 15%
  5. Configuration (wrong preset, missing input/output) - 5%
Always check threading and session state BEFORE debugging capture logic.
核心原则:当相机无法正常工作时,问题通常出在以下几个方面:
  1. 线程处理(在主线程处理会话操作)- 占比35%
  2. 会话生命周期(未启动、被中断、配置错误)- 占比25%
  3. 旋转处理(使用已废弃API、缺少协调器)- 占比20%
  4. 权限问题(权限被拒绝、未申请权限)- 占比15%
  5. 配置错误(预设参数错误、缺少输入/输出)- 占比5%
排查逻辑问题前,务必先检查线程处理和会话状态。

Red Flags

异常信号

Symptoms that indicate camera-specific issues:
SymptomLikely Cause
Preview shows black screenSession not started, permission denied, no camera input
UI freezes when opening camera
startRunning()
called on main thread
Camera freezes on phone callNo interruption handling
Preview rotated 90° wrongNot using RotationCoordinator (iOS 17+)
Captured photo rotated wrongRotation angle not applied to output connection
Front camera photo not mirroredThis is correct! (preview mirrors, photo does not)
"Camera in use by another app"Another app has exclusive access
Capture takes 2+ seconds
photoQualityPrioritization
set to
.quality
Session won't start on iPadSplit View - camera unavailable
Crash on older iOSUsing iOS 17+ APIs without availability check
以下症状表明存在相机相关问题:
症状可能原因
预览显示黑屏会话未启动、权限被拒绝、未添加相机输入
打开相机时UI卡顿在主线程调用
startRunning()
来电时相机卡顿未处理中断事件
预览画面旋转90°错误未使用RotationCoordinator(iOS 17+)
拍摄的照片旋转错误未将旋转角度应用到输出连接
前置摄像头拍摄的照片未镜像这是正常现象!(预览是镜像的,拍摄的照片不是)
"相机被其他应用占用"其他应用正在独占相机
拍摄耗时2秒以上
photoQualityPrioritization
设置为
.quality
iPad上会话无法启动分屏模式下相机不可用
旧版iOS系统崩溃使用了iOS 17+ API但未做版本兼容性检查

Mandatory First Steps

必做初步排查步骤

Before investigating code, run these diagnostics:
在检查代码之前,先执行以下诊断步骤:

Step 1: Check Session State

步骤1:检查会话状态

swift
print("📷 Session state:")
print("  isRunning: \(session.isRunning)")
print("  inputs: \(session.inputs.count)")
print("  outputs: \(session.outputs.count)")

for input in session.inputs {
    if let deviceInput = input as? AVCaptureDeviceInput {
        print("  Input: \(deviceInput.device.localizedName)")
    }
}

for output in session.outputs {
    print("  Output: \(type(of: output))")
}
Expected output:
  • ✅ isRunning: true, inputs ≥ 1, outputs ≥ 1 → Session working
  • ⚠️ isRunning: false → Session not started or interrupted
  • ❌ inputs: 0 → Camera not added (permission? configuration?)
swift
print("📷 Session state:")
print("  isRunning: \(session.isRunning)")
print("  inputs: \(session.inputs.count)")
print("  outputs: \(session.outputs.count)")

for input in session.inputs {
    if let deviceInput = input as? AVCaptureDeviceInput {
        print("  Input: \(deviceInput.device.localizedName)")
    }
}

for output in session.outputs {
    print("  Output: \(type(of: output))")
}
预期输出
  • ✅ isRunning: true, inputs ≥ 1, outputs ≥ 1 → 会话正常运行
  • ⚠️ isRunning: false → 会话未启动或已中断
  • ❌ inputs: 0 → 未添加相机输入(权限问题?配置错误?)

Step 2: Check Threading

步骤2:检查线程处理

swift
print("🧵 Thread check:")

// When setting up session
sessionQueue.async {
    print("  Setup thread: \(Thread.isMainThread ? "❌ MAIN" : "✅ Background")")
}

// When starting session
sessionQueue.async {
    print("  Start thread: \(Thread.isMainThread ? "❌ MAIN" : "✅ Background")")
}
Expected output:
  • ✅ All background → Correct
  • ❌ Any main thread → UI will freeze
swift
print("🧵 Thread check:")

// 配置会话时
sessionQueue.async {
    print("  Setup thread: \(Thread.isMainThread ? "❌ MAIN" : "✅ Background")")
}

// 启动会话时
sessionQueue.async {
    print("  Start thread: \(Thread.isMainThread ? "❌ MAIN" : "✅ Background")")
}
预期输出
  • ✅ 全部在后台线程 → 正确
  • ❌ 任何操作在主线程 → UI会卡顿

Step 3: Check Permissions

步骤3:检查权限

swift
let status = AVCaptureDevice.authorizationStatus(for: .video)
print("🔐 Camera permission: \(status.rawValue)")

switch status {
case .authorized: print("  ✅ Authorized")
case .notDetermined: print("  ⚠️ Not yet requested")
case .denied: print("  ❌ Denied by user")
case .restricted: print("  ❌ Restricted (parental controls?)")
@unknown default: print("  ❓ Unknown")
}
swift
let status = AVCaptureDevice.authorizationStatus(for: .video)
print("🔐 Camera permission: \(status.rawValue)")

switch status {
case .authorized: print("  ✅ 已授权")
case .notDetermined: print("  ⚠️ 尚未申请")
case .denied: print("  ❌ 被用户拒绝")
case .restricted: print("  ❌ 受限制(如家长控制)")
@unknown default: print("  ❓ 未知状态")
}

Step 4: Check for Interruptions

步骤4:检查中断事件

swift
// Add temporary observer to see interruptions
NotificationCenter.default.addObserver(
    forName: .AVCaptureSessionWasInterrupted,
    object: session,
    queue: .main
) { notification in
    if let reason = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? Int {
        print("🚨 Interrupted: reason \(reason)")
    }
}
swift
// 添加临时观察者以查看中断情况
NotificationCenter.default.addObserver(
    forName: .AVCaptureSessionWasInterrupted,
    object: session,
    queue: .main
) { notification in
    if let reason = notification.userInfo?[AVCaptureSessionInterruptionReasonKey] as? Int {
        print("🚨 Interrupted: reason \(reason)")
    }
}

Decision Tree

决策树

Camera not working as expected?
├─ Black/frozen preview?
│  ├─ Check Step 1 (session state)
│  │  ├─ isRunning = false → See Pattern 1 (session not started)
│  │  ├─ inputs = 0 → See Pattern 2 (no camera input)
│  │  └─ isRunning = true, inputs > 0 → See Pattern 3 (preview layer)
├─ UI freezes when opening camera?
│  └─ Check Step 2 (threading)
│     └─ Main thread → See Pattern 4 (move to session queue)
├─ Camera freezes during use?
│  ├─ After phone call → See Pattern 5 (interruption handling)
│  ├─ In Split View (iPad) → See Pattern 6 (multitasking)
│  └─ Random freezes → See Pattern 7 (thermal pressure)
├─ Preview/photo rotated wrong?
│  ├─ Preview rotated → See Pattern 8 (RotationCoordinator preview)
│  ├─ Captured photo rotated → See Pattern 9 (capture rotation)
│  └─ Front camera "wrong" → See Pattern 10 (mirroring expected)
├─ Capture too slow?
│  ├─ 2+ seconds delay → See Pattern 11 (quality prioritization)
│  └─ Slight delay → See Pattern 12 (deferred processing)
├─ Permission issues?
│  ├─ Status: notDetermined → See Pattern 13 (request permission)
│  └─ Status: denied → See Pattern 14 (settings prompt)
└─ Crash on some devices?
   └─ See Pattern 15 (API availability)
相机工作异常?
├─ 预览黑屏/卡顿?
│  ├─ 检查步骤1(会话状态)
│  │  ├─ isRunning = false → 查看模式1(会话未启动)
│  │  ├─ inputs = 0 → 查看模式2(无相机输入)
│  │  └─ isRunning = true, inputs > 0 → 查看模式3(预览层问题)
├─ 打开相机时UI卡顿?
│  └─ 检查步骤2(线程处理)
│     └─ 在主线程执行 → 查看模式4(移至会话队列)
├─ 使用过程中相机卡顿?
│  ├─ 来电后发生 → 查看模式5(中断事件处理)
│  ├─ iPad分屏模式下 → 查看模式6(多任务处理)
│  └─ 随机卡顿 → 查看模式7(过热压力)
├─ 预览/照片旋转错误?
│  ├─ 预览画面旋转 → 查看模式8(RotationCoordinator预览处理)
│  ├─ 拍摄照片旋转 → 查看模式9(拍摄旋转处理)
│  └─ 前置摄像头显示异常 → 查看模式10(正常镜像逻辑)
├─ 拍摄速度慢?
│  ├─ 延迟2秒以上 → 查看模式11(质量优先级设置)
│  └─ 轻微延迟 → 查看模式12(延迟处理)
├─ 权限问题?
│  ├─ 状态:notDetermined → 查看模式13(申请权限)
│  └─ 状态:denied → 查看模式14(引导至设置)
└─ 部分设备崩溃?
   └─ 查看模式15(API兼容性)

Diagnostic Patterns

诊断模式

Pattern 1: Session Not Started

模式1:会话未启动

Symptom: Black preview,
isRunning = false
Common causes:
  1. startRunning()
    never called
  2. startRunning()
    called but session has no inputs
  3. Session stopped and never restarted
Diagnostic:
swift
// Check if startRunning was called
print("isRunning before start: \(session.isRunning)")
session.startRunning()
print("isRunning after start: \(session.isRunning)")
Fix:
swift
// Ensure session is started on session queue
func startSession() {
    sessionQueue.async { [self] in
        guard !session.isRunning else { return }

        // Verify we have inputs before starting
        guard !session.inputs.isEmpty else {
            print("❌ Cannot start - no inputs configured")
            return
        }

        session.startRunning()
    }
}
Time to fix: 10 min
症状:预览黑屏,
isRunning = false
常见原因
  1. 从未调用
    startRunning()
  2. 调用了
    startRunning()
    但会话无输入源
  3. 会话已停止但未重新启动
诊断代码
swift
// 检查是否调用了startRunning
print("isRunning before start: \(session.isRunning)")
session.startRunning()
print("isRunning after start: \(session.isRunning)")
修复方案
swift
// 确保在会话队列中启动会话
func startSession() {
    sessionQueue.async { [self] in
        guard !session.isRunning else { return }

        // 启动前验证是否有输入源
        guard !session.inputs.isEmpty else {
            print("❌ 无法启动 - 未配置输入源")
            return
        }

        session.startRunning()
    }
}
修复时间:10分钟

Pattern 2: No Camera Input

模式2:无相机输入源

Symptom:
session.inputs.count = 0
Common causes:
  1. Camera permission denied
  2. AVCaptureDeviceInput
    creation failed
  3. canAddInput()
    returned false
  4. Configuration not committed
Diagnostic:
swift
// Step through input setup
guard let camera = AVCaptureDevice.default(for: .video) else {
    print("❌ No camera device found")
    return
}
print("✅ Camera: \(camera.localizedName)")

do {
    let input = try AVCaptureDeviceInput(device: camera)
    print("✅ Input created")

    if session.canAddInput(input) {
        print("✅ Can add input")
    } else {
        print("❌ Cannot add input - check session preset compatibility")
    }
} catch {
    print("❌ Input creation failed: \(error)")
}
Fix: Ensure permission is granted BEFORE creating input, and wrap in configuration block:
swift
session.beginConfiguration()
// Add input here
session.commitConfiguration()
Time to fix: 15 min
症状
session.inputs.count = 0
常见原因
  1. 相机权限被拒绝
  2. AVCaptureDeviceInput
    创建失败
  3. canAddInput()
    返回false
  4. 配置未提交
诊断代码
swift
// 逐步检查输入源设置
guard let camera = AVCaptureDevice.default(for: .video) else {
    print("❌ 未找到相机设备")
    return
}
print("✅ 相机: \(camera.localizedName)")

do {
    let input = try AVCaptureDeviceInput(device: camera)
    print("✅ 输入源创建成功")

    if session.canAddInput(input) {
        print("✅ 可以添加输入源")
    } else {
        print("❌ 无法添加输入源 - 检查会话预设兼容性")
    }
} catch {
    print("❌ 输入源创建失败: \(error)")
}
修复方案:确保在创建输入源前已获取权限,并将配置包裹在配置块中:
swift
session.beginConfiguration()
// 在此处添加输入源
session.commitConfiguration()
修复时间:15分钟

Pattern 3: Preview Layer Not Connected

模式3:预览层未连接

Symptom:
isRunning = true
, inputs configured, but preview is black
Common causes:
  1. Preview layer session not set
  2. Preview layer not in view hierarchy
  3. Preview layer frame is zero
Diagnostic:
swift
print("Preview layer session: \(previewLayer.session != nil)")
print("Preview layer superlayer: \(previewLayer.superlayer != nil)")
print("Preview layer frame: \(previewLayer.frame)")
print("Preview layer connection: \(previewLayer.connection != nil)")
Fix:
swift
// Ensure preview layer is properly configured
previewLayer.session = session
previewLayer.videoGravity = .resizeAspectFill

// Ensure frame is set (common in SwiftUI)
previewLayer.frame = view.bounds
Time to fix: 10 min
症状
isRunning = true
,已配置输入源,但预览黑屏
常见原因
  1. 预览层未绑定会话
  2. 预览层未加入视图层级
  3. 预览层帧大小为0
诊断代码
swift
print("Preview layer session: \(previewLayer.session != nil)")
print("Preview layer superlayer: \(previewLayer.superlayer != nil)")
print("Preview layer frame: \(previewLayer.frame)")
print("Preview layer connection: \(previewLayer.connection != nil)")
修复方案
swift
// 确保预览层配置正确
previewLayer.session = session
previewLayer.videoGravity = .resizeAspectFill

// 确保帧大小已设置(SwiftUI中常见问题)
previewLayer.frame = view.bounds
修复时间:10分钟

Pattern 4: Main Thread Blocking

模式4:主线程阻塞

Symptom: UI freezes for 1-3 seconds when camera opens
Root cause:
startRunning()
is a blocking call executed on main thread
Diagnostic:
swift
// If this prints on main thread, that's the problem
print("startRunning on thread: \(Thread.current)")
session.startRunning()
Fix:
swift
// Create dedicated serial queue
private let sessionQueue = DispatchQueue(label: "camera.session")

func startSession() {
    sessionQueue.async { [self] in
        session.startRunning()
    }
}
Time to fix: 15 min
症状:打开相机时UI卡顿1-3秒
根本原因
startRunning()
是阻塞调用,在主线程执行
诊断代码
swift
// 如果此打印显示在主线程,就是问题所在
print("startRunning on thread: \(Thread.current)")
session.startRunning()
修复方案
swift
// 创建专用串行队列
private let sessionQueue = DispatchQueue(label: "camera.session")

func startSession() {
    sessionQueue.async { [self] in
        session.startRunning()
    }
}
修复时间:15分钟

Pattern 5: Phone Call Interruption

模式5:来电中断

Symptom: Camera works, then freezes when phone call comes in
Root cause: Session interrupted but no handling/UI feedback
Diagnostic:
swift
// Check if session is still running after returning from call
print("Session running: \(session.isRunning)")
// Will be false during active call, true after call ends
Fix: Add interruption observers (see camera-capture skill Pattern 5)
Key point: Session AUTOMATICALLY resumes after interruption ends. You don't need to call
startRunning()
again. Just update your UI.
Time to fix: 30 min
症状:相机正常工作,来电后卡顿
根本原因:会话被中断但未处理/未给出UI反馈
诊断代码
swift
// 检查通话结束后会话是否仍在运行
print("Session running: \(session.isRunning)")
// 通话进行中为false,通话结束后恢复为true
修复方案:添加中断事件观察者(参考相机拍摄技能模式5)
关键点:会话在中断结束后会自动恢复。无需再次调用
startRunning()
,只需更新UI即可。
修复时间:30分钟

Pattern 6: Split View Camera Unavailable

模式6:分屏模式下相机不可用

Symptom: Camera stops working when iPad enters Split View
Root cause: Camera not available with multiple foreground apps
Diagnostic:
swift
// Check interruption reason
// InterruptionReason.videoDeviceNotAvailableWithMultipleForegroundApps
Fix: Show appropriate UI message and resume when user exits Split View:
swift
case .videoDeviceNotAvailableWithMultipleForegroundApps:
    showMessage("Camera unavailable in Split View. Use full screen.")
Time to fix: 15 min
症状:iPad进入分屏模式后相机停止工作
根本原因:多前台应用时相机不可用
诊断代码
swift
// 检查中断原因
// InterruptionReason.videoDeviceNotAvailableWithMultipleForegroundApps
修复方案:显示合适的UI提示,用户退出分屏模式后恢复:
swift
case .videoDeviceNotAvailableWithMultipleForegroundApps:
    showMessage("分屏模式下相机不可用,请使用全屏模式。")
修复时间:15分钟

Pattern 7: Thermal Pressure

模式7:过热压力

Symptom: Camera stops randomly, especially after prolonged use
Root cause: Device getting hot, system reducing resources
Diagnostic:
swift
// Check thermal state
print("Thermal state: \(ProcessInfo.processInfo.thermalState.rawValue)")
// 0 = nominal, 1 = fair, 2 = serious, 3 = critical
Fix: Reduce quality or show cooling message:
swift
case .videoDeviceNotAvailableDueToSystemPressure:
    // Reduce quality
    session.sessionPreset = .medium
    showMessage("Camera quality reduced due to device temperature")
Time to fix: 20 min
症状:相机随机停止工作,尤其是长时间使用后
根本原因:设备过热,系统减少资源分配
诊断代码
swift
// 检查热状态
print("Thermal state: \(ProcessInfo.processInfo.thermalState.rawValue)")
// 0 = 正常, 1 = 良好, 2 = 严重, 3 = 危急
修复方案:降低画质或显示降温提示:
swift
case .videoDeviceNotAvailableDueToSystemPressure:
    // 降低画质
    session.sessionPreset = .medium
    showMessage("由于设备温度过高,相机画质已降低")
修复时间:20分钟

Pattern 8: Preview Rotation Wrong

模式8:预览画面旋转错误

Symptom: Preview is rotated 90° from expected
Root cause: Not using RotationCoordinator (iOS 17+) or not observing updates
Diagnostic:
swift
print("Preview connection rotation: \(previewLayer.connection?.videoRotationAngle ?? -1)")
Fix:
swift
// Create and observe RotationCoordinator
let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: previewLayer)

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

// Observe changes
observation = coordinator.observe(\.videoRotationAngleForHorizonLevelPreview) { [weak previewLayer] coord, _ in
    DispatchQueue.main.async {
        previewLayer?.connection?.videoRotationAngle = coord.videoRotationAngleForHorizonLevelPreview
    }
}
Time to fix: 30 min
症状:预览画面与预期旋转90°
根本原因:未使用RotationCoordinator(iOS 17+)或未观察更新
诊断代码
swift
print("Preview connection rotation: \(previewLayer.connection?.videoRotationAngle ?? -1)")
修复方案
swift
// 创建并观察RotationCoordinator
let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: previewLayer)

// 设置初始旋转角度
previewLayer.connection?.videoRotationAngle = coordinator.videoRotationAngleForHorizonLevelPreview

// 观察角度变化
observation = coordinator.observe(\.videoRotationAngleForHorizonLevelPreview) { [weak previewLayer] coord, _ in
    DispatchQueue.main.async {
        previewLayer?.connection?.videoRotationAngle = coord.videoRotationAngleForHorizonLevelPreview
    }
}
修复时间:30分钟

Pattern 9: Captured Photo Rotation Wrong

模式9:拍摄照片旋转错误

Symptom: Preview looks correct, but captured photo is rotated
Root cause: Rotation angle not applied to photo output connection
Diagnostic:
swift
if let connection = photoOutput.connection(with: .video) {
    print("Photo connection rotation: \(connection.videoRotationAngle)")
}
Fix:
swift
func capturePhoto() {
    // Apply current rotation to capture
    if let connection = photoOutput.connection(with: .video) {
        connection.videoRotationAngle = rotationCoordinator.videoRotationAngleForHorizonLevelCapture
    }

    photoOutput.capturePhoto(with: settings, delegate: self)
}
Time to fix: 15 min
症状:预览显示正常,但拍摄的照片旋转错误
根本原因:未将旋转角度应用到照片输出连接
诊断代码
swift
if let connection = photoOutput.connection(with: .video) {
    print("Photo connection rotation: \(connection.videoRotationAngle)")
}
修复方案
swift
func capturePhoto() {
    // 拍摄时应用当前旋转角度
    if let connection = photoOutput.connection(with: .video) {
        connection.videoRotationAngle = rotationCoordinator.videoRotationAngleForHorizonLevelCapture
    }

    photoOutput.capturePhoto(with: settings, delegate: self)
}
修复时间:15分钟

Pattern 10: Front Camera Mirroring

模式10:前置摄像头镜像逻辑

Symptom: Designer says "front camera photo doesn't match preview"
Reality: This is CORRECT behavior, not a bug.
Explanation:
  • Preview is mirrored (like looking in a mirror - user expectation)
  • Captured photo is NOT mirrored (text reads correctly when shared)
  • This matches the system Camera app behavior
If business requires mirrored photos (selfie apps):
swift
func mirrorImage(_ image: UIImage) -> UIImage? {
    guard let cgImage = image.cgImage else { return nil }
    return UIImage(cgImage: cgImage, scale: image.scale, orientation: .upMirrored)
}
Time to fix: 5 min (explanation) or 15 min (if mirroring required)
症状:设计师反馈“前置摄像头拍摄的照片与预览不一致”
实际情况:这是正确的行为,不是Bug。
解释
  • 预览是镜像的(像照镜子一样,符合用户预期)
  • 拍摄的照片不是镜像的(分享时文字可正常阅读)
  • 这与系统相机应用的行为一致
如果业务需求需要镜像照片(如自拍应用):
swift
func mirrorImage(_ image: UIImage) -> UIImage? {
    guard let cgImage = image.cgImage else { return nil }
    return UIImage(cgImage: cgImage, scale: image.scale, orientation: .upMirrored)
}
修复时间:5分钟(仅解释)或15分钟(如需实现镜像)

Pattern 11: Slow Capture (Quality Priority)

模式11:拍摄缓慢(质量优先级设置)

Symptom: Photo capture takes 2+ seconds
Root cause:
photoQualityPrioritization = .quality
(default for some devices)
Diagnostic:
swift
print("Max quality prioritization: \(photoOutput.maxPhotoQualityPrioritization.rawValue)")
// Check what you're requesting in AVCapturePhotoSettings
Fix:
swift
var settings = AVCapturePhotoSettings()

// For fast capture (social/sharing)
settings.photoQualityPrioritization = .speed

// For balanced (general use)
settings.photoQualityPrioritization = .balanced

// Only use .quality when image quality is critical
Time to fix: 5 min
症状:照片拍摄耗时2秒以上
根本原因
photoQualityPrioritization = .quality
(部分设备默认设置)
诊断代码
swift
print("Max quality prioritization: \(photoOutput.maxPhotoQualityPrioritization.rawValue)")
// 检查AVCapturePhotoSettings中的设置
修复方案
swift
var settings = AVCapturePhotoSettings()

// 快速拍摄(社交/分享场景)
settings.photoQualityPrioritization = .speed

// 平衡模式(通用场景)
settings.photoQualityPrioritization = .balanced

// 仅在画质要求极高时使用.quality
修复时间:5分钟

Pattern 12: Deferred Processing

模式12:延迟处理

Symptom: Want maximum responsiveness (zero-shutter-lag)
Solution: Enable deferred processing (iOS 17+)
swift
photoOutput.isAutoDeferredPhotoDeliveryEnabled = true

// Then handle proxy in delegate:
// - didFinishProcessingPhoto gives proxy for immediate display
// - didFinishCapturingDeferredPhotoProxy gives final image later
Time to fix: 30 min
症状:需要最高响应速度(零快门延迟)
解决方案:启用延迟处理(iOS 17+)
swift
photoOutput.isAutoDeferredPhotoDeliveryEnabled = true

// 然后在代理中处理:
// - didFinishProcessingPhoto 提供代理用于即时显示
// - didFinishCapturingDeferredPhotoProxy 稍后提供最终图像
修复时间:30分钟

Pattern 13: Permission Not Requested

模式13:未申请权限

Symptom:
authorizationStatus = .notDetermined
Fix:
swift
// Must request before setting up session
Task {
    let granted = await AVCaptureDevice.requestAccess(for: .video)
    if granted {
        setupSession()
    }
}
Time to fix: 10 min
症状
authorizationStatus = .notDetermined
修复方案
swift
// 必须在配置会话前申请权限
Task {
    let granted = await AVCaptureDevice.requestAccess(for: .video)
    if granted {
        setupSession()
    }
}
修复时间:10分钟

Pattern 14: Permission Denied

模式14:权限被拒绝

Symptom:
authorizationStatus = .denied
Fix: Show settings prompt
swift
func showSettingsPrompt() {
    let alert = UIAlertController(
        title: "Camera Access Required",
        message: "Please enable camera access in Settings to use this feature.",
        preferredStyle: .alert
    )
    alert.addAction(UIAlertAction(title: "Settings", style: .default) { _ in
        if let url = URL(string: UIApplication.openSettingsURLString) {
            UIApplication.shared.open(url)
        }
    })
    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel))
    present(alert, animated: true)
}
Time to fix: 15 min
症状
authorizationStatus = .denied
修复方案:显示设置引导提示
swift
func showSettingsPrompt() {
    let alert = UIAlertController(
        title: "需要相机权限",
        message: "请在设置中启用相机权限以使用此功能。",
        preferredStyle: .alert
    )
    alert.addAction(UIAlertAction(title: "设置", style: .default) { _ in
        if let url = URL(string: UIApplication.openSettingsURLString) {
            UIApplication.shared.open(url)
        }
    })
    alert.addAction(UIAlertAction(title: "取消", style: .cancel))
    present(alert, animated: true)
}
修复时间:15分钟

Pattern 15: API Availability Crash

模式15:API兼容性崩溃

Symptom: Crash on iOS 16 or earlier
Root cause: Using iOS 17+ APIs without availability check
Fix:
swift
if #available(iOS 17.0, *) {
    // Use RotationCoordinator
    let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: preview)
} else {
    // Fallback to deprecated videoOrientation
    if let connection = previewLayer.connection {
        connection.videoOrientation = .portrait
    }
}
Time to fix: 20 min
症状:iOS 16或更早版本崩溃
根本原因:使用了iOS 17+ API但未做版本兼容性检查
修复方案
swift
if #available(iOS 17.0, *) {
    // 使用RotationCoordinator
    let coordinator = AVCaptureDevice.RotationCoordinator(device: camera, previewLayer: preview)
} else {
    // 回退到已废弃的videoOrientation
    if let connection = previewLayer.connection {
        connection.videoOrientation = .portrait
    }
}
修复时间:20分钟

Quick Reference Table

快速参考表

SymptomCheck FirstLikely Pattern
Black previewStep 1 (session state)1, 2, or 3
UI freezesStep 2 (threading)4
Freezes on callStep 4 (interruptions)5
Wrong rotationPrint rotation angle8 or 9
Slow capturePrint quality setting11
Denied accessStep 3 (permissions)14
Crash on old iOSCheck @available15
症状优先检查对应模式
预览黑屏步骤1(会话状态)1、2或3
UI卡顿步骤2(线程处理)4
来电后卡顿步骤4(中断事件)5
旋转错误打印旋转角度8或9
拍摄缓慢打印质量设置11
权限被拒绝步骤3(权限检查)14
旧版iOS崩溃检查@available15

Checklist

检查清单

Before escalating camera issues:
Basics:
  • ☑ Session has at least one input
  • ☑ Session has at least one output
  • ☑ Session isRunning = true
  • ☑ Preview layer connected to session
  • ☑ Preview layer has non-zero frame
Threading:
  • ☑ All session work on sessionQueue
  • ☑ startRunning() on background thread
  • ☑ UI updates on main thread
Permissions:
  • ☑ Authorization status checked
  • ☑ Permission requested if notDetermined
  • ☑ Graceful UI for denied state
Rotation:
  • ☑ RotationCoordinator created with device AND previewLayer
  • ☑ Observation set up for preview angle changes
  • ☑ Capture angle applied when taking photos
Interruptions:
  • ☑ Interruption observer registered
  • ☑ UI feedback for interrupted state
  • ☑ Tested with incoming phone call
上报相机问题前,请确认:
基础项
  • ☑ 会话至少有一个输入源
  • ☑ 会话至少有一个输出源
  • ☑ 会话isRunning = true
  • ☑ 预览层已绑定会话
  • ☑ 预览层帧大小非零
线程处理
  • ☑ 所有会话操作在sessionQueue执行
  • ☑ startRunning()在后台线程执行
  • ☑ UI更新在主线程执行
权限
  • ☑ 已检查授权状态
  • ☑ 未确定状态时已申请权限
  • ☑ 权限被拒绝时有友好UI提示
旋转处理
  • ☑ RotationCoordinator已关联设备和预览层
  • ☑ 已设置预览角度变化的观察
  • ☑ 拍摄时已应用旋转角度
中断事件
  • ☑ 已注册中断事件观察者
  • ☑ 中断状态有UI反馈
  • ☑ 已测试来电场景

Resources

参考资源

WWDC: 2021-10247, 2023-10105
Docs: /avfoundation/avcapturesession, /avfoundation/avcapturesessionwasinterruptednotification
Skills: axiom-camera-capture, axiom-camera-capture-ref
WWDC:2021-10247, 2023-10105
文档:/avfoundation/avcapturesession, /avfoundation/avcapturesessionwasinterruptednotification
技能:axiom-camera-capture, axiom-camera-capture-ref