SwiftUI+AV Foundation开启闪光灯拍照报错-16800求助
问题:SwiftUI中使用AVFoundation开启闪光灯拍照报错,无法获取图片数据
错误信息
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSUnderlyingError=0x28004e790 {Error Domain=NSOSStatusErrorDomain Code=-16800 "(null)"}, NSLocalizedFailureReason=An unknown error occurred (-16800), AVErrorRecordingFailureDomainKey=4, NSLocalizedDescription=The operation could not be completed}
问题表现:开启闪光灯(flashMode = .on)时触发上述错误,且picData为空导致后续解包失败;关闭闪光灯(flashMode = .off)时拍照正常,但无法使用闪光灯。
原问题代码
import SwiftUI import AVFoundation struct Camera: View { var body: some View { CameraView() } } struct Camera_Previews: PreviewProvider { static var previews: some View { ContentView() } } // Test code: Ignore struct globalVariable { public var isBack = false } class GlobalModel : ObservableObject { @Published var isBack = false func get() -> Bool{ return isBack } func setTrue() { isBack = true } func setFalse() { isBack = false } } // struct CameraView: View { // Creates the camera preview elements @StateObject var camera = CameraModel() @State var img : UIImage? = nil @State var navigated = false @ObservedObject var nextScreen = GlobalModel() var body: some View{ ZStack{ CameraPreview(camera: camera) .ignoresSafeArea(.all, edges: .all) VStack{ Spacer() HStack{ if camera.isTaken { Button(action: { camera.reTake() self.nextScreen.setFalse() print(nextScreen.get()) }, label: { Text("Retake").foregroundColor(.black) .fontWeight(.semibold) .padding(.vertical, 10) .padding(.horizontal, 30) .background(Color.white) .clipShape(Capsule()) }).padding(.trailing) Spacer() ZStack{ NavigationLink("", destination: Classify(originalImage: img, label: "", confidence: 0.0), isActive: $navigated) Button(action: {if !camera.isLoaded{ img = camera.savePic() if img != nil{ print("is not nil") } self.navigated.toggle() self.nextScreen.setTrue() print(nextScreen.get()) } }, label: { Text("Continue").foregroundColor(.black) .fontWeight(.semibold) .padding(.vertical, 10) .padding(.horizontal, 30) .background(Color.white) .clipShape(Capsule()) }).padding(.leading).opacity(nextScreen.get() ? 0.01 : 1) } } else{ Button(action: camera.takePic, label: { ZStack{ Image(systemName: "camera.circle") .frame(width: 70, height: 75).font(.system(size: 60)) } }) } }.frame(height: 75) } }.onAppear(perform: { UIDevice.current.setValue(UIInterfaceOrientation.portrait.rawValue, forKey: "orientation") // Forcing the rotation to portrait AppDelegate.orientationLock = .portrait // And making sure it stays that way //UITabBar.appearance().isHidden = true camera.Check() }) .onDisappear(){ AppDelegate.orientationLock = .all UITabBar.appearance().isHidden = false } } } class CameraModel: NSObject, ObservableObject, AVCapturePhotoCaptureDelegate { @Published var isTaken = false @Published var session = AVCaptureSession() @Published var alert = false @Published var output = AVCapturePhotoOutput() @Published var preview : AVCaptureVideoPreviewLayer! @Published var isLoaded = false @Published var picData = Data(count: 0) var flashMode: AVCaptureDevice.FlashMode = .on // set the camera to on var device : AVCaptureDevice? // for camera device private func getSettings(camera: AVCaptureDevice, flashMode: AVCaptureDevice.FlashMode) -> AVCapturePhotoSettings { let settings = AVCapturePhotoSettings() // get the default settings and change them to enable flash if camera.hasFlash { settings.flashMode = self.flashMode } return settings } func Check() { switch AVCaptureDevice.authorizationStatus(for: .video){ case .authorized: setUp() return case .notDetermined: AVCaptureDevice.requestAccess(for: .video) { (status) in if status{ self.setUp() } } case .denied: self.alert.toggle() return default: return } } func setUp(){ do{ self.session.beginConfiguration() self.device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) let input = try AVCaptureDeviceInput(device: self.device!) if self.session.canAddInput(input){ self.session.addInput(input) } if self.session.canAddOutput(self.output){ self.session.addOutput(self.output) } self.session.commitConfiguration() } catch{ print(error.localizedDescription) } } func takePic(){ DispatchQueue.global(qos: .background).async { let currentSettings = self.getSettings(camera: self.device!, flashMode: self.flashMode) self.output.capturePhoto(with: currentSettings, delegate: self) // Capture photo with flash settings; doesn't work DispatchQueue.main.async { Timer.scheduledTimer(withTimeInterval: 0.1, repeats: false){ (timer) in self.session.stopRunning() //isBack.setTrue() } } } DispatchQueue.main.async { withAnimation{ self.isTaken.toggle() } } } func reTake() { DispatchQueue.global(qos: .background).async { self.session.startRunning() DispatchQueue.main.async { withAnimation{ self.isTaken.toggle() self.isLoaded = false } } } } func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if error != nil{ print(error!) } print("photoOuput function") print(photo) guard let imageData = photo.fileDataRepresentation() else{return } self.picData = imageData } func savePic () -> UIImage{ let image = UIImage(data: self.picData)! self.isLoaded = true return image } } struct CameraPreview: UIViewRepresentable { @ObservedObject var camera : CameraModel func makeUIView(context: Context) -> some UIView { let view = UIView(frame: UIScreen.main.bounds) camera.preview = AVCaptureVideoPreviewLayer(session: camera.session) camera.preview.frame = view.frame camera.preview.videoGravity = .resizeAspectFill view.layer.addSublayer(camera.preview) camera.session.startRunning() return view } func updateUIView(_ uiView: UIViewType, context: Context) { } }
解决方案
1. 正确配置摄像头设备的闪光灯模式
错误码-16800通常是因为闪光灯设备未被正确锁定和配置。修改setUp方法,在配置session时先锁定设备,设置闪光灯模式后再解锁:
func setUp(){ do{ self.session.beginConfiguration() guard let device = AVCaptureDevice.default(.builtInWideAngleCamera, for: .video, position: .back) else { print("无法获取后置摄像头") return } self.device = device // 锁定设备并配置闪光灯 try device.lockForConfiguration() if device.hasFlash { device.flashMode = self.flashMode } device.unlockForConfiguration() let input = try AVCaptureDeviceInput(device: device) if self.session.canAddInput(input){ self.session.addInput(input) } if self.session.canAddOutput(self.output){ self.session.addOutput(self.output) } self.session.commitConfiguration() } catch{ print(error.localizedDescription) } }
2. 优化拍照设置方法
移除冗余参数,确保照片设置兼容设备,并正确传递闪光灯模式:
private func getSettings() -> AVCapturePhotoSettings { let settings = AVCapturePhotoSettings() if let device = self.device, device.hasFlash { settings.flashMode = self.flashMode } // 添加预览格式配置,避免兼容性问题 if let previewFormat = settings.availablePreviewPhotoPixelFormatTypes.first { settings.previewPhotoFormat = [kCVPixelBufferPixelFormatTypeKey as String: previewFormat] } return settings }
3. 避免过早停止捕获会话
原代码中0.1秒后强制停止session,会导致照片还未处理完成就中断。改为在photoOutput回调处理完图片后再停止session:
func takePic(){ DispatchQueue.global(qos: .background).async { let currentSettings = self.getSettings() self.output.capturePhoto(with: currentSettings, delegate: self) } DispatchQueue.main.async { withAnimation{ self.isTaken.toggle() } } }
更新photoOutput方法,处理错误并在合适时机停止session:
func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) { if let error = error { print("拍照错误: \(error.localizedDescription)") DispatchQueue.main.async { self.isTaken = false } return } print("photoOutput function") print(photo) guard let imageData = photo.fileDataRepresentation() else { print("无法获取图片数据") DispatchQueue.main.async { self.isTaken = false } return } DispatchQueue.main.async { self.picData = imageData self.session.stopRunning() } }
4. 修复图片保存方法的强制解包
避免因picData为空导致崩溃,改为可选返回UIImage:
func savePic() -> UIImage? { guard let image = UIImage(data: self.picData) else { print("图片数据无效") return nil } self.isLoaded = true return image }
同时在CameraView中处理可选值:
Button(action: { if !camera.isLoaded { if let image = camera.savePic() { img = image print("is not nil") self.navigated.toggle() self.nextScreen.setTrue() print(nextScreen.get()) } else { print("保存图片失败") } } }, label: { Text("Continue").foregroundColor(.black) .fontWeight(.semibold) .padding(.vertical, 10) .padding(.horizontal, 30) .background(Color.white) .clipShape(Capsule()) }).padding(.leading).opacity(nextScreen.get() ? 0.01 : 1)
内容的提问来源于stack exchange,提问作者Manan Vij
相关产品推荐
相关产品推荐

