swiftavcam

Getting Photo Data from AVCapturePhotoCaptureDelegate


I'm currently trying to build a camera application for fun and ran into a problem.

I was using an example that Apple provides you for the process of taking a photo or video. Found here:

https://developer.apple.com/library/content/samplecode/AVCam/Introduction/Intro.html

I'm having a problem using the PhotoCaptureDelegate. So currently when the user takes a photo it will save it to the users photo library. What I would like to do, is after it captures the photo I want to store the photoData Object of the photo. So that I can display the photo on another view as a preview before you save it.

This is what Apple suggests to use:

  func capture(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureForResolvedSettings resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
    if let error = error {
        print("Error capturing photo: \(error)")
        didFinish()
        return
    }

    guard let photoData = photoData else {
        print("No photo data resource")
        didFinish()
        return
    }

    PHPhotoLibrary.requestAuthorization { [unowned self] status in
        if status == .authorized {
            PHPhotoLibrary.shared().performChanges({ [unowned self] in
                    let creationRequest = PHAssetCreationRequest.forAsset()
                    creationRequest.addResource(with: .photo, data: photoData, options: nil)

                    if let livePhotoCompanionMovieURL = self.livePhotoCompanionMovieURL {
                        let livePhotoCompanionMovieFileResourceOptions = PHAssetResourceCreationOptions()
                        livePhotoCompanionMovieFileResourceOptions.shouldMoveFile = true
                        creationRequest.addResource(with: .pairedVideo, fileURL: livePhotoCompanionMovieURL, options: livePhotoCompanionMovieFileResourceOptions)
                    }

                }, completionHandler: { [unowned self] success, error in
                    if let error = error {
                        print("Error occurered while saving photo to photo library: \(error)")
                    }

                    self.didFinish()
                }
            )
        }
        else {
            self.didFinish()
        }
    }
}

How would I take the photoData object from my delegate and pass it back to my view controller, that is calling on this delegate?


Solution

  • There are a couple ways to solve this. You could also have your view controller own the photo capture delegate (like Apple's example) and pass it back up after the photo is taken. You could also just have your view controller be the photo capture delegate and not worry about passing anything!

    1) Pass the photo back up

    There are (again) a couple ways to do this. You could have the delegate pass the value back up to the controller. You could also have the delegate tell the controller it is done, and have the controller come grab the photo. I'll explain the second way below.

    Looking at Apple's example, we can see that the PhotoCaptureDelegate already tells the controller when it is done! Whenever a PhotoCaptureDelegate object is created, it is passed a completed block.

    To to allow the controller to grab the photo, we just have to make the photo object public!

    1. Change the private var photoData: Data? = nil in PhotoCaptureDelegate to be public var photoData: Data? = nil
    2. In CameraViewController when you define the completed block, you now have the photo data:
        ...
        , completed: { [unowned self] photoCaptureDelegate in
            self.sessionQueue.async { [unowned self] in
                self.inProgressPhotoCaptureDelegates[photoCaptureDelegate.requestedPhotoSettings.uniqueID] = nil
                if let photoData = photoCaptureDelegate.photoData {
                    // You now have the photo data!
                    // You can pass this to another method in the Controller now                
                }
            }
        }
        ...
    

    2) View Controller as the AVCapturePhotoCaptureDelegate

    Another way to solve this is to just have your ViewController be the AVCapturePhotoCaptureDelegate. Then you'll already have the photo data in the view controller! It could look something like this:

    class MyViewController: UIViewController, AVCapturePhotoCaptureDelegate {
    
        ...
    
        func capture(_ captureOutput: AVCapturePhotoOutput, didFinishCaptureForResolvedSettings resolvedSettings: AVCaptureResolvedPhotoSettings, error: Error?) {
            guard let photoData = photoData else {
                return
            }
            // You now have your photo data!
        }
    }