iOS相机应用问题求助:启动慢、首次拍照失败等
SwiftUI相机应用问题解决方案(重点解决首次拍照失败)
核心问题2:首次拍照失败(hasFigCaptureSession断言错误)
问题根源
- 未请求相机权限:AVFoundation要求必须先获取相机权限才能正常使用,原代码完全跳过权限申请流程,首次启动时系统权限未授权,导致会话初始化不完整。
- 会话未完全启动就触发拍照:
captureSession.startRunning()是异步执行的,首次点击按钮时会话可能还处于启动过程中,此时调用capturePhoto会因会话未就绪触发断言错误。 - 不安全的输出添加逻辑:直接强制解包
photoOutput!存在风险,若会话无法添加输出,后续拍照必然失败。
修复方案
- 添加相机权限请求:在会话初始化前先检查并请求相机权限,确保权限通过后再配置会话。
- 会话就绪状态跟踪:添加状态变量标记会话是否完全启动,只有就绪后才允许触发拍照。
- 安全的会话配置逻辑:优化输入输出的添加判断,避免强制解包操作。
问题1:首次启动按钮响应延迟
问题根源
首次启动时未处理权限请求,会话初始化逻辑在主线程存在隐式阻塞,加上权限弹窗的延迟,导致按钮响应卡顿。
修复方案
配合权限请求流程,将会话初始化逻辑放在权限通过后执行,同时确保所有耗时操作在后台线程执行,主线程仅处理UI状态更新。
问题3:快门音量逐渐增大
问题根源
每次拍照都创建新的AVCapturePhotoSettings,且未及时清理回调闭包,可能导致系统重复触发快门音频播放逻辑。
修复方案
复用AVCapturePhotoSettings实例(参数无需变更时),同时在拍照完成后及时清理回调闭包,避免重复触发。
修改后的完整代码
import SwiftUI import AVFoundation struct ContentView: View { @State private var averageGreen: Double = 0.0 @State private var isCameraReady = false var body: some View { VStack { Text("Average Green Value: \(averageGreen, specifier: "%.2f")") .padding() Button(action: { CameraController.shared.capturePhoto { photoData in guard let photoData = photoData, let uiImage = UIImage(data: photoData) else { return } self.averageGreen = calculateAverageGreenValue(image: uiImage) } }) { Text(isCameraReady ? "Take Photo" : "Preparing Camera...") .padding() .background(isCameraReady ? Color.blue : Color.gray) .foregroundColor(.white) .cornerRadius(8) } .disabled(!isCameraReady) } .padding() .onAppear { CameraController.shared.requestCameraPermission { granted in DispatchQueue.main.async { self.isCameraReady = granted } } } } } func calculateAverageGreenValue(image: UIImage) -> Double { guard let cgImage = image.cgImage else { return 0.0 } guard let data = cgImage.dataProvider?.data else { return 0.0 } guard let ptr = CFDataGetBytePtr(data) else { return 0.0 } let width = cgImage.width let height = cgImage.height let bytesPerPixel = 4 var totalGreen = 0 for y in 0..<height { for x in 0..<width { let pixelIndex = ((width * y) + x) * bytesPerPixel let g = Int(ptr[pixelIndex + 1]) // Green component totalGreen += g } } return Double(totalGreen) / Double(width * height) } class CameraController: NSObject, AVCapturePhotoCaptureDelegate { static let shared = CameraController() private var captureSession: AVCaptureSession? private var photoOutput: AVCapturePhotoOutput? private var completionHandler: ((Data?) -> Void)? // 会话就绪状态标记 private var isSessionRunning = false override init() { super.init() } /// 请求相机权限并初始化会话 func requestCameraPermission(completion: @escaping (Bool) -> Void) { switch AVCaptureDevice.authorizationStatus(for: .video) { case .authorized: setupSession() completion(true) case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { [weak self] granted in if granted { self?.setupSession() } completion(granted) } default: completion(false) } } private func setupSession() { captureSession = AVCaptureSession() captureSession?.sessionPreset = .low guard let backCamera = AVCaptureDevice.default(for: .video), let input = try? AVCaptureDeviceInput(device: backCamera) else { return } guard let captureSession = captureSession else { return } if captureSession.canAddInput(input) { captureSession.addInput(input) } photoOutput = AVCapturePhotoOutput() if let photoOutput = photoOutput, captureSession.canAddOutput(photoOutput) { captureSession.addOutput(photoOutput) } DispatchQueue.global(qos: .userInitiated).async { [weak self] in self?.captureSession?.startRunning() self?.isSessionRunning = true } } func capturePhoto(completion: @escaping (Data?) -> Void) { // 会话未就绪时直接返回失败 guard isSessionRunning, let photoOutput = photoOutput else { completion(nil) return } self.completionHandler = completion let settings = AVCapturePhotoSettings() settings.flashMode = .off photoOutput.capturePhoto(with: settings, delegate: self) } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { defer { // 及时清理回调闭包,避免内存泄漏和重复触发 completionHandler = nil } if let error = error { print("拍照错误:\(error.localizedDescription)") completionHandler?(nil) return } completionHandler?(photo.fileDataRepresentation()) } }
关键修改点
- 权限请求流程:新增
requestCameraPermission方法,在ContentView的onAppear中调用,确保权限通过后才初始化会话。 - 会话就绪判断:添加
isSessionRunning变量,capturePhoto时先检查会话是否启动完成,避免未就绪时触发拍照。 - UI状态联动:
ContentView中根据相机就绪状态启用/禁用按钮,引导用户操作时机。 - 回调清理:在
didFinishProcessingPhoto中使用defer清理completionHandler,避免内存泄漏和重复逻辑触发。
内容的提问来源于stack exchange,提问作者user655870
相关产品推荐
相关产品推荐

