camera freezes, preview rotated wrong, capture slow, session interrupted, black preview, front camera mirrored, camera not starting, AVCaptureSession errors, startRunning blocks, phone call interrupts camera
/plugin marketplace add CharlesWiltgen/Axiom/plugin install axiom@axiom-marketplaceThis skill inherits all available tools. When active, it can use any tool Claude has access to.
Systematic troubleshooting for AVFoundation camera issues: frozen preview, wrong rotation, slow capture, session interruptions, and permission problems.
Core Principle: When camera doesn't work, the problem is usually:
Always check threading and session state BEFORE debugging capture logic.
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 | startRunning() called on main thread |
| 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 | photoQualityPrioritization set to .quality |
| Session won't start on iPad | Split View - camera unavailable |
| Crash on older iOS | Using iOS 17+ APIs without availability check |
Before investigating code, run these diagnostics:
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:
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:
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")
}
// 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)")
}
}
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)
Symptom: Black preview, isRunning = false
Common causes:
startRunning() never calledstartRunning() called but session has no inputsDiagnostic:
// Check if startRunning was called
print("isRunning before start: \(session.isRunning)")
session.startRunning()
print("isRunning after start: \(session.isRunning)")
Fix:
// 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
Symptom: session.inputs.count = 0
Common causes:
AVCaptureDeviceInput creation failedcanAddInput() returned falseDiagnostic:
// 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:
session.beginConfiguration()
// Add input here
session.commitConfiguration()
Time to fix: 15 min
Symptom: isRunning = true, inputs configured, but preview is black
Common causes:
Diagnostic:
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:
// 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
Symptom: UI freezes for 1-3 seconds when camera opens
Root cause: startRunning() is a blocking call executed on main thread
Diagnostic:
// If this prints on main thread, that's the problem
print("startRunning on thread: \(Thread.current)")
session.startRunning()
Fix:
// Create dedicated serial queue
private let sessionQueue = DispatchQueue(label: "camera.session")
func startSession() {
sessionQueue.async { [self] in
session.startRunning()
}
}
Time to fix: 15 min
Symptom: Camera works, then freezes when phone call comes in
Root cause: Session interrupted but no handling/UI feedback
Diagnostic:
// 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
Symptom: Camera stops working when iPad enters Split View
Root cause: Camera not available with multiple foreground apps
Diagnostic:
// Check interruption reason
// InterruptionReason.videoDeviceNotAvailableWithMultipleForegroundApps
Fix: Show appropriate UI message and resume when user exits Split View:
case .videoDeviceNotAvailableWithMultipleForegroundApps:
showMessage("Camera unavailable in Split View. Use full screen.")
Time to fix: 15 min
Symptom: Camera stops randomly, especially after prolonged use
Root cause: Device getting hot, system reducing resources
Diagnostic:
// 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:
case .videoDeviceNotAvailableDueToSystemPressure:
// Reduce quality
session.sessionPreset = .medium
showMessage("Camera quality reduced due to device temperature")
Time to fix: 20 min
Symptom: Preview is rotated 90ยฐ from expected
Root cause: Not using RotationCoordinator (iOS 17+) or not observing updates
Diagnostic:
print("Preview connection rotation: \(previewLayer.connection?.videoRotationAngle ?? -1)")
Fix:
// 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
Symptom: Preview looks correct, but captured photo is rotated
Root cause: Rotation angle not applied to photo output connection
Diagnostic:
if let connection = photoOutput.connection(with: .video) {
print("Photo connection rotation: \(connection.videoRotationAngle)")
}
Fix:
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
Symptom: Designer says "front camera photo doesn't match preview"
Reality: This is CORRECT behavior, not a bug.
Explanation:
If business requires mirrored photos (selfie apps):
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)
Symptom: Photo capture takes 2+ seconds
Root cause: photoQualityPrioritization = .quality (default for some devices)
Diagnostic:
print("Max quality prioritization: \(photoOutput.maxPhotoQualityPrioritization.rawValue)")
// Check what you're requesting in AVCapturePhotoSettings
Fix:
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
Symptom: Want maximum responsiveness (zero-shutter-lag)
Solution: Enable deferred processing (iOS 17+)
photoOutput.isAutoDeferredPhotoDeliveryEnabled = true
// Then handle proxy in delegate:
// - didFinishProcessingPhoto gives proxy for immediate display
// - didFinishCapturingDeferredPhotoProxy gives final image later
Time to fix: 30 min
Symptom: authorizationStatus = .notDetermined
Fix:
// Must request before setting up session
Task {
let granted = await AVCaptureDevice.requestAccess(for: .video)
if granted {
setupSession()
}
}
Time to fix: 10 min
Symptom: authorizationStatus = .denied
Fix: Show settings prompt
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
Symptom: Crash on iOS 16 or earlier
Root cause: Using iOS 17+ APIs without availability check
Fix:
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
| 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 |
Before escalating camera issues:
Basics:
Threading:
Permissions:
Rotation:
Interruptions:
WWDC: 2021-10247, 2023-10105
Docs: /avfoundation/avcapturesession, /avfoundation/avcapturesessionwasinterruptednotification
Skills: axiom-camera-capture, axiom-camera-capture-ref
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.