You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.25 08:06:27