Swiftui photoOutput for Images not called every time

318 Views Asked by At

This is actually the same issue as this post AVCapturePhotoCaptureDelegate photoOutput() not called every time however no one responded to that . I find that on takePic the photoOutput function is called sometimes and not others it is literally 50/50 . I am using Swiftui 2.0 . Does anyone know a work around this or why this issue is happening ? The code to replicate this is actually quite small .It is the code below and then setting permissions in the info.plist for Privacy - Camera usage description and privacy - photo library usage description . I have tried different things and it is literally still a 50/50 on whether photoOutput gets called . When it is not called you will see this in log print("Nil on SavePic:picData") Any suggestions would be great .

import SwiftUI
import AVFoundation

struct CreateStoryView: View {
    @StateObject var camera = CameraModel()
    @Environment(\.presentationMode) var presentationMode
    var body: some View {
      
        ZStack {
            // Going to be Camera Preview
           CameraPreview(camera: camera)
                .ignoresSafeArea(.all, edges: .all)
            VStack {
                HStack {
                    Spacer()
                    Image(systemName: "arrowshape.turn.up.backward.circle.fill")
                        .foregroundColor(.black)
                        .padding(.trailing,20)
                        .background(Color.white)
                        .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)
                        .onTapGesture {
                            if camera.session.isRunning == true {
                                camera.session.stopRunning()
                          
                            }
                            self.presentationMode.wrappedValue.dismiss()
                        }
                if camera.isTaken {
               
                        Button(action: camera.reTake, label: { // camera.reTake
                    Image(systemName: "arrow.triangle.2.circlepath.camera")
                        .foregroundColor(.black)
                        .padding()
                        .background(Color.white)
                        .clipShape(/*@START_MENU_TOKEN@*/Circle()/*@END_MENU_TOKEN@*/)
                    })
                    .padding(.trailing,10)
                    }
                }
                
                Spacer()
                HStack{
                   // If Taken then showing save and again take button
                    if camera.isTaken{
                        Button(action: {if !camera.isSaved{camera.savePic()}}, label: {
                            Text(camera.isSaved ? "Saved" : "Save")
                            .foregroundColor(.black)
                            .fontWeight(.semibold)
                            .padding(.vertical,10)
                            .padding(.horizontal,20)
                            .background(Color.white)
                            .clipShape(Capsule())
                        })
                        .padding(.leading)
                        Spacer()
                        
                    } else {
                        Button(action: camera.takePic , label: {
                            ZStack{
                                Circle()
                                    .fill(Color.white)
                                    .frame(width: 65, height: 65)
                                
                                Circle()
                                    .stroke(Color.white,lineWidth: 2)
                                    .frame(width: 75, height: 75)
                            }
                        })
                    }
                }.frame(height: 75)
            }
        }.onAppear(perform: {
            camera.Check()
        })
    }
}

// Camera Model

class CameraModel: NSObject,ObservableObject,AVCapturePhotoCaptureDelegate {
    @Published var isTaken = false
    @Published var session = AVCaptureSession()
    @Published var alert = false
    @Published var output = AVCapturePhotoOutput()
    // preview ...
    @Published var preview: AVCaptureVideoPreviewLayer!
    @Published var isSaved = false
    @Published var picData = Data(count: 0)
    func Check() {
    // first checking camera has permission
        
        switch AVCaptureDevice.authorizationStatus(for: .video) {
        case .authorized:
            setUp()
            return
        case .notDetermined:
            //retrusting for permission
        
            AVCaptureDevice.requestAccess(for: .video) {(status) in
                
                if status{
                    self.setUp()
                }
                
            }
        case .denied:
            self.alert.toggle()
            return
        default:
            return
        }
        
    }
    func setUp() {
        // setting up camera
        
        do{
            // setting configs...
            self.session.beginConfiguration()
            
            // change for your own
            
            let device = AVCaptureDevice.default(.builtInDualCamera,for: .video,position: .back)
            
            let input = try AVCaptureDeviceInput(device: device!)
            // checking and adding session
            if self.session.canAddInput(input) {
                self.session.addInput(input)
            }
            // same for output
            if (self.session.canAddOutput(self.output)) {
                self.session.addOutput(self.output)
            }
    
            self.session.commitConfiguration()
        } catch {
            print(error.localizedDescription)
        }
    }
    // take and retake
    func takePic(){
        DispatchQueue.global(qos: .background).async {

            self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
            self.session.stopRunning()
            DispatchQueue.main.async {
                withAnimation{ self.isTaken.toggle() }
        }
       }
    }
    
    func reTake() {
        DispatchQueue.global(qos: .background).async {
            self.session.startRunning()
            DispatchQueue.main.async {
              //  withAnimation{ self.isTaken.toggle() }
                // clearing
                //self.isSaved = false
                self.isTaken = false
        }
        }
    }
    
    
    func photoOutput(_ output: AVCapturePhotoOutput, didFinishProcessingPhoto photo: AVCapturePhoto, error: Error?) {
        print("photoOutput check")
        if error != nil {
            return
        }
        guard var imageData = photo.fileDataRepresentation() else {return}
        self.picData = imageData
        if isSaved == true {
        if !imageData.isEmpty {
           imageData.removeAll()
            isSaved = false
        }
        } else {
            isSaved = true
        }
        
    }
    func savePic() {
        if UIImage(data: self.picData) == nil {
            print("Nil on SavePic:picData")
            return
        }
        let image = UIImage(data: self.picData)!
        
        // saving image
        UIImageWriteToSavedPhotosAlbum(image, self, #selector(saveError), nil)
        self.isSaved = true
        print("saved sucessfully")
    }
    
    @objc func saveError(_ image: UIImage, didFinishSavingWithError error: Error?, contextInfo: UnsafeRawPointer) {
            print("Save finished!")
        }
}

// setting up view for preview

struct CameraPreview: UIViewRepresentable {
    @ObservedObject var camera: CameraModel
    
    func makeUIView(context: Context) ->  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: UIView, context: Context) {
        
    }
}




struct CreateStoryView_Previews: PreviewProvider {
    static var previews: some View {
        CreateStoryView()
    }
}
1

There are 1 best solutions below

0
On

You stop the session before the output delegate has called. Try this code:

func takePic(){
    self.output.capturePhoto(with: AVCapturePhotoSettings(), delegate: self)
    DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 0.2) {
         self.session.stopRunning()
         DispatchQueue.main.async {
             withAnimation{
                 self.isTaken.toggle()
             }
         }
     }
}