axiom-camera-capture-diag
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseCamera 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:
- Threading (session work on main thread) - 35%
- Session lifecycle (not started, interrupted, not configured) - 25%
- Rotation (deprecated APIs, missing coordinator) - 20%
- Permissions (denied, not requested) - 15%
- Configuration (wrong preset, missing input/output) - 5%
Always check threading and session state BEFORE debugging capture logic.
核心原则:当相机无法正常工作时,问题通常出在以下几个方面:
- 线程处理(在主线程处理会话操作)- 占比35%
- 会话生命周期(未启动、被中断、配置错误)- 占比25%
- 旋转处理(使用已废弃API、缺少协调器)- 占比20%
- 权限问题(权限被拒绝、未申请权限)- 占比15%
- 配置错误(预设参数错误、缺少输入/输出)- 占比5%
排查逻辑问题前,务必先检查线程处理和会话状态。
Red Flags
异常信号
Symptoms that indicate camera-specific issues:
| Symptom | Likely Cause |
|---|---|
| Preview shows black screen | Session not started, permission denied, no camera input |
| UI freezes when opening camera | |
| Camera freezes on phone call | No interruption handling |
| Preview rotated 90° wrong | Not using RotationCoordinator (iOS 17+) |
| Captured photo rotated wrong | Rotation angle not applied to output connection |
| Front camera photo not mirrored | This is correct! (preview mirrors, photo does not) |
| "Camera in use by another app" | Another app has exclusive access |
| Capture takes 2+ seconds | |
| Session won't start on iPad | Split View - camera unavailable |
| Crash on older iOS | Using iOS 17+ APIs without availability check |
以下症状表明存在相机相关问题:
| 症状 | 可能原因 |
|---|---|
| 预览显示黑屏 | 会话未启动、权限被拒绝、未添加相机输入 |
| 打开相机时UI卡顿 | 在主线程调用 |
| 来电时相机卡顿 | 未处理中断事件 |
| 预览画面旋转90°错误 | 未使用RotationCoordinator(iOS 17+) |
| 拍摄的照片旋转错误 | 未将旋转角度应用到输出连接 |
| 前置摄像头拍摄的照片未镜像 | 这是正常现象!(预览是镜像的,拍摄的照片不是) |
| "相机被其他应用占用" | 其他应用正在独占相机 |
| 拍摄耗时2秒以上 | |
| 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 = falseCommon causes:
- never called
startRunning() - called but session has no inputs
startRunning() - 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常见原因:
- 从未调用
startRunning() - 调用了但会话无输入源
startRunning() - 会话已停止但未重新启动
诊断代码:
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 = 0Common causes:
- Camera permission denied
- creation failed
AVCaptureDeviceInput - returned false
canAddInput() - 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常见原因:
- 相机权限被拒绝
- 创建失败
AVCaptureDeviceInput - 返回false
canAddInput() - 配置未提交
诊断代码:
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: , inputs configured, but preview is black
isRunning = trueCommon causes:
- Preview layer session not set
- Preview layer not in view hierarchy
- 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.boundsTime to fix: 10 min
症状:,已配置输入源,但预览黑屏
isRunning = true常见原因:
- 预览层未绑定会话
- 预览层未加入视图层级
- 预览层帧大小为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: is a blocking call executed on main thread
startRunning()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 endsFix: Add interruption observers (see camera-capture skill Pattern 5)
Key point: Session AUTOMATICALLY resumes after interruption ends. You don't need to call again. Just update your UI.
startRunning()Time to fix: 30 min
症状:相机正常工作,来电后卡顿
根本原因:会话被中断但未处理/未给出UI反馈
诊断代码:
swift
// 检查通话结束后会话是否仍在运行
print("Session running: \(session.isRunning)")
// 通话进行中为false,通话结束后恢复为true修复方案:添加中断事件观察者(参考相机拍摄技能模式5)
关键点:会话在中断结束后会自动恢复。无需再次调用,只需更新UI即可。
startRunning()修复时间: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.videoDeviceNotAvailableWithMultipleForegroundAppsFix: 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 = criticalFix: 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: (default for some devices)
photoQualityPrioritization = .qualityDiagnostic:
swift
print("Max quality prioritization: \(photoOutput.maxPhotoQualityPrioritization.rawValue)")
// Check what you're requesting in AVCapturePhotoSettingsFix:
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 criticalTime 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 laterTime to fix: 30 min
症状:需要最高响应速度(零快门延迟)
解决方案:启用延迟处理(iOS 17+)
swift
photoOutput.isAutoDeferredPhotoDeliveryEnabled = true
// 然后在代理中处理:
// - didFinishProcessingPhoto 提供代理用于即时显示
// - didFinishCapturingDeferredPhotoProxy 稍后提供最终图像修复时间:30分钟
Pattern 13: Permission Not Requested
模式13:未申请权限
Symptom:
authorizationStatus = .notDeterminedFix:
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 = .deniedFix: 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
快速参考表
| Symptom | Check First | Likely Pattern |
|---|---|---|
| Black preview | Step 1 (session state) | 1, 2, or 3 |
| UI freezes | Step 2 (threading) | 4 |
| Freezes on call | Step 4 (interruptions) | 5 |
| Wrong rotation | Print rotation angle | 8 or 9 |
| Slow capture | Print quality setting | 11 |
| Denied access | Step 3 (permissions) | 14 |
| Crash on old iOS | Check @available | 15 |
| 症状 | 优先检查 | 对应模式 |
|---|---|---|
| 预览黑屏 | 步骤1(会话状态) | 1、2或3 |
| UI卡顿 | 步骤2(线程处理) | 4 |
| 来电后卡顿 | 步骤4(中断事件) | 5 |
| 旋转错误 | 打印旋转角度 | 8或9 |
| 拍摄缓慢 | 打印质量设置 | 11 |
| 权限被拒绝 | 步骤3(权限检查) | 14 |
| 旧版iOS崩溃 | 检查@available | 15 |
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