自定义相机拍照时崩溃:Thread 1遇Delegate nil解包错误
问题描述
已成功实现带有实时预览功能的自定义相机,但尝试拍照时出现以下错误:
Thread 1: Fatal error: Unexpectedly found nil while unwrapping an Optional value
错误发生在CameraService的capturePhoto方法中强制解包delegate!的位置。
以下是相关代码:
CameraService.swift
class CameraService { var session: AVCaptureSession? var delegate: AVCapturePhotoCaptureDelegate? let output = AVCapturePhotoOutput() let previewLayer = AVCaptureVideoPreviewLayer() func start(delegate: AVCapturePhotoCaptureDelegate, completion:@escaping (Error?) -> ()) { self.delegate = delegate self.checkPermissions(completion: completion) } private func checkPermissions(completion:@escaping (Error?) -> ()){ switch AVCaptureDevice.authorizationStatus(for: .video){ case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in guard granted else { return } DispatchQueue.main.async { self?.setupCamera(completion: completion) } } case .restricted: break case .denied: break case .authorized: setupCamera(completion: completion) @unknown default: break } } private func setupCamera(completion:@escaping (Error?) -> ()) { let session = AVCaptureSession() if let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front){ do { let input = try AVCaptureDeviceInput(device: device) if session.canAddInput(input) { session.addInput(input) } if session.canAddOutput(output) { session.addOutput(output) } previewLayer.videoGravity = .resizeAspectFill previewLayer.session = session DispatchQueue.global(qos:.userInitiated).async { session.startRunning() } self.session = session } catch { completion(error) } } } func capturePhoto(with settings: AVCapturePhotoSettings = AVCapturePhotoSettings()) { output.capturePhoto(with: settings, delegate: delegate!) // 错误发生处 } }
CameraView.swift
struct CameraView: UIViewControllerRepresentable { typealias UIViewControllerType = UIViewController let cameraService: CameraService let didFinishProcessingPhoto: (Result<AVCapturePhoto, Error>) -> () func makeUIViewController(context: Context) -> UIViewController { cameraService.start(delegate: context.coordinator) { err in if let err = err { didFinishProcessingPhoto(.failure(err)) return } } let viewController = UIViewController() viewController.view.backgroundColor = .black viewController.view.layer.addSublayer(cameraService.previewLayer) cameraService.previewLayer.frame = viewController.view.bounds return viewController } func makeCoordinator() -> Coordinator { Coordinator(self, didFinishProcessingPhoto: didFinishProcessingPhoto) } func updateUIViewController(_ uiViewController: UIViewController, context: Context) { } class Coordinator: NSObject, AVCapturePhotoCaptureDelegate { let parent: CameraView private var didFinishProcessingPhoto: (Result<AVCapturePhoto, Error>) -> () init(_ parent: CameraView, didFinishProcessingPhoto: @escaping (Result<AVCapturePhoto, Error>) -> ()) { self.parent = parent self.didFinishProcessingPhoto = didFinishProcessingPhoto } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let error = error { didFinishProcessingPhoto(.failure(error)) return } didFinishProcessingPhoto(.success(photo)) } } }
CustomCameraView.swift
import SwiftUI import AVFoundation struct CustomCameraView: View { let cameraService = CameraService() @Binding var capturedImage: UIImage? @Environment(\.presentationMode) private var presentationMode var body: some View { ZStack { CameraView(cameraService: cameraService, didFinishProcessingPhoto: { result in switch result { case .success(let photo): if let data = photo.fileDataRepresentation() { capturedImage = UIImage(data: data) } else { print("Error: no image data found.") } case .failure(let err): print(err.localizedDescription) } }) .frame(maxWidth:.infinity, maxHeight: .infinity) .ignoresSafeArea() VStack{ Button { cameraService.capturePhoto() } label: { Circle() .fill(.white) .frame(width: 60, height:60) } } } } }
解决方案
核心问题分析
崩溃的直接原因是CameraService中的delegate属性为nil,但你在capturePhoto中使用了强制解包delegate!。可能的触发场景包括:
- 用户在相机权限请求尚未完成时就点击拍照按钮
- 权限被拒绝/受限后,
start方法未正确处理,但用户仍能点击拍照 - 极端情况下
Coordinator被提前释放导致delegate变为nil
修复步骤
1. 安全解包delegate并添加前置检查
修改CameraService的capturePhoto方法,避免强制解包,同时检查输出是否正常:
func capturePhoto(with settings: AVCapturePhotoSettings = AVCapturePhotoSettings()) { // 检查delegate是否存在 guard let captureDelegate = self.delegate else { print("错误:拍照代理未设置,无法执行拍照操作") return } // 检查照片输出是否已连接到视频会话 guard output.connection(with: .video) != nil else { print("错误:照片输出未与视频会话建立连接") return } output.capturePhoto(with: settings, delegate: captureDelegate) }
2. 完善权限处理逻辑
在checkPermissions中补充权限拒绝/受限的错误回调,让上层可以提示用户:
private func checkPermissions(completion:@escaping (Error?) -> ()){ switch AVCaptureDevice.authorizationStatus(for: .video){ case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in guard let self = self else { return } DispatchQueue.main.async { if granted { self.setupCamera(completion: completion) } else { completion(NSError(domain: "CameraPermission", code: -1, userInfo: [NSLocalizedDescriptionKey: "相机权限已被拒绝"])) } } } case .restricted: completion(NSError(domain: "CameraPermission", code: -2, userInfo: [NSLocalizedDescriptionKey: "相机权限受限制"])) case .denied: completion(NSError(domain: "CameraPermission", code: -3, userInfo: [NSLocalizedDescriptionKey: "相机权限已被拒绝"])) case .authorized: setupCamera(completion: completion) @unknown default: completion(NSError(domain: "CameraPermission", code: -4, userInfo: [NSLocalizedDescriptionKey: "未知的相机权限状态"])) } }
3. 完善相机初始化的错误处理
在setupCamera中补充设备不存在、无法添加输入/输出的错误回调:
private func setupCamera(completion:@escaping (Error?) -> ()) { let session = AVCaptureSession() // 检查是否有可用的前置摄像头 guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .front) else { completion(NSError(domain: "CameraSetup", code: -1, userInfo: [NSLocalizedDescriptionKey: "无可用的前置摄像头"])) return } do { let input = try AVCaptureDeviceInput(device: device) guard session.canAddInput(input) else { completion(NSError(domain: "CameraSetup", code: -2, userInfo: [NSLocalizedDescriptionKey: "无法将相机输入添加到会话"])) return } session.addInput(input) guard session.canAddOutput(output) else { completion(NSError(domain: "CameraSetup", code: -3, userInfo: [NSLocalizedDescriptionKey: "无法将照片输出添加到会话"])) return } session.addOutput(output) previewLayer.videoGravity = .resizeAspectFill previewLayer.session = session DispatchQueue.global(qos:.userInitiated).async { session.startRunning() } self.session = session completion(nil) } catch { completion(error) } }
4. 上层添加错误提示(可选)
在CustomCameraView的错误回调中添加用户提示,比如用弹窗告知权限问题:
CameraView(cameraService: cameraService, didFinishProcessingPhoto: { result in switch result { case .success(let photo): if let data = photo.fileDataRepresentation() { capturedImage = UIImage(data: data) } else { print("Error: no image data found.") } case .failure(let err): print(err.localizedDescription) // 这里可以添加UIAlertController或SwiftUI的弹窗提示用户 } })
内容的提问来源于stack exchange,提问作者Luka Šalipur
相关产品推荐
相关产品推荐

