Search code examples
iosswiftuiimagepickercontrollerswift4

UIImagePickerControllerDelegate not called


I have a standalone class to fetch an image from the camera/photo library using UIImagePickerController.

I cannot seem to figure out why the didFinishPickingMediaWithInfo or the imagePickerControllerDidCancel methods are not called..? I realise this question has been asked many times but I cannot figure it out... I know that all the method names are correct.

I have properly set all of the following in the info.plist:

  • Privacy - Media Library Usage Description
  • Privacy - Photo Library Usage Description
  • Privacy - Photo Library Usage Description

import UIKit

class ImagePickerService: NSObject, UIImagePickerControllerDelegate, UINavigationControllerDelegate {

  fileprivate var completion: ((UIImage) -> ())
  fileprivate var imagePicker: UIImagePickerController

  init(completion: @escaping ((UIImage) -> ())) {
    self.completion = completion
    self.imagePicker = UIImagePickerController()
    super.init()
    imagePicker.delegate = self
  }

  func show(from viewController: UIViewController) {
    showAlert(from: viewController)
  }

  fileprivate func showAlert(from viewController: UIViewController) {
    let alert = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)
    alert.addAction(UIAlertAction(title: "Take a photo", style: .default) { _ in
      self.openCamera(from: viewController)
    })
    alert.addAction(UIAlertAction(title: "Choose from library", style: .default) { _ in
      self.openGallery(from: viewController)
    })
    alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil))
    viewController.present(alert, animated: true, completion: nil)
  }

  // MARK: Image Picker

  fileprivate func openCamera(from viewController: UIViewController) {
    guard UIImagePickerController.isSourceTypeAvailable(.camera) else {
      viewController.showOKAlert(title: "Error!", message: "No camera available")
      return
    }
    showImagePicker(withSource: .camera, from: viewController)
  }

  fileprivate func openGallery(from viewController: UIViewController) {
    showImagePicker(withSource: .photoLibrary, from: viewController)
  }

  fileprivate func showImagePicker(withSource source: UIImagePickerControllerSourceType, from viewController: UIViewController) {
    imagePicker.sourceType = source
    viewController.present(imagePicker, animated: true, completion: nil)
  }

}

extension ImagePickerService {

  func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else {
      return
    }
    completion(image)
    picker.dismiss(animated: true, completion: nil)
  }

  func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
    picker.dismiss(animated: true, completion: nil)
  }

}

EDIT: The problem was that I was not creating a strong reference to the ImagePickerService on instantiation and it was released automatically before the delegate methods could be called.


Solution

  • The problem is in the following code snippet:

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        guard let image = info[UIImagePickerControllerEditedImage] as? UIImage else {
          return
        }
        completion(image)
        picker.dismiss(animated: true, completion: nil)
    }
    

    You are expecting that there is always an edited image, which may not be the case for all photos in your photo library. Try using the UIImagePickerControllerOriginalImage key instead when info[UIImagePickerControllerEditedImage] returns nil.

    If these methods aren't called in the first place, you need to make sure you have a strong reference to the ImagePickerService instance.

    For example, this code works:

    class ViewController: UIViewController {
    
        var picker: ImagePickerService?
    
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
    
            DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) { [weak self] in
                guard let `self` = self else { return }
                self.picker = ImagePickerService() { (image) in
                    print(image)
                }
                self.picker?.show(from: self)
            }
        }
    
    }