14

On the surface I thought that this had to be a delegate issue, but after asking for the delegate the right one was returned.

I created an ImagePicker class to handle all the UIImagePickerController stuff. Every thing works until the delegate methods need to be called. After I pick a photo, the imagePicker dismisses, but the didFinishPickingMediaWithInfo method never gets called. Please help! Thanks :)

func selectPhoto() {
    imagePicker.delegate = self //Delegate gets set here

    let photoAsk = UIAlertController.init( //Ask user if they want to take picture or choose one
        title: "Edit Profile Picture",
        message: nil,
        preferredStyle: .alert)
    let cameraAction = UIAlertAction.init(
        title: "Take Photo",
        style: .default) { (UIAlertAction) in
            if (UIImagePickerController.isSourceTypeAvailable(.camera)) {
                self.imagePicker.sourceType = .camera
                UIApplication.topViewController()!.present(self.imagePicker, animated: true, completion:nil)
            } else {
                print("Cannot access camera in simulator.")
                return
            }
    }
    let photoLibraryAction = UIAlertAction.init(
        title: "Photo Library",
        style: .default) { (UIAlertAction) in
            self.imagePicker.sourceType = .photoLibrary
            UIApplication.topViewController()!.present(self.imagePicker, animated: true, completion:nil)
            print("UIImagePickerDelegate: \(self.imagePicker.delegate.debugDescription)") // <--THIS PRINTS OUT "AppName.ImagePicker: 0x145d7bdf0>", and the class name is ImagePicker
    }
    let cancelAction = UIAlertAction.init(
        title: "Cancel",
        style: .cancel) { (UIAlertAction) in return }

    photoAsk.addAction(cameraAction)
    photoAsk.addAction(photoLibraryAction)
    photoAsk.addAction(cancelAction)

    imagePicker.mediaTypes = [kUTTypeImage as String]

    UIApplication.topViewController()?.present(photoAsk, animated: true, completion: nil)
    }
}

This never gets called:

func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    print("Image picked.") //NEVER PRINTS
}
Clayton C.
  • 863
  • 1
  • 8
  • 17
  • 1
    Try to set `imagePicker.delegate = self`in `viewDidLoad`instead – Rashwan L Sep 25 '16 at 02:57
  • @RashwanL It's a custom class, but I tried putting it in the class's `init` method and there's no difference. – Clayton C. Sep 25 '16 at 03:09
  • I'm confused. Where is `selectPhoto()` being called? – Aaron Sep 25 '16 at 03:54
  • 1
    Is there a strong reference anywhere to the instance of this custom class posted in your question? – rmaddy Sep 25 '16 at 05:45
  • @Aaron - `selectPhoto()` is being called from a `UIViewController`. That view controller has a circular `UIImageView` with a `UITapGestureRecognizer` on top. When tapped, it creates an instance of `ImageHelper` and calls the method. Sorry for not including that. – Clayton C. Sep 25 '16 at 19:22
  • 1
    @ClaytonAndrewCohn Did you got any solution? I am facing the same issue with ImageHelper class. – Pooja Gupta May 07 '18 at 06:19

10 Answers10

21

I had to copy the method names straight from the delegate. For some reason the auto-complete has the method headers wrong.

public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
    if let image = info[UIImagePickerControllerOriginalImage] as? UIImage {
        //save image
        //display image
    }
    self.dismiss(animated: true, completion: nil)
}

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

UPDATE SWIFT 5:

public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
    if let image = info[UIImagePickerController.InfoKey.originalImage] as? UIImage {
        //save image
        //display image
    }
    self.dismiss(animated: true, completion: nil)
}
Alk
  • 5,215
  • 8
  • 47
  • 116
Dustin Spengler
  • 5,478
  • 4
  • 28
  • 36
  • 2
    You are the best! I was trying too hard to figure out the issue but the delegate wasn't simply being called. Reason :: method name was slightly different : What I had earlier :: func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) { What works : func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) { Unfortunately, the delegates are optional so if you have a slight mistake in method, you would never be able to debug why its not working. – Shivam Pokhriyal Apr 02 '19 at 08:24
  • 2
    They seem to have changed the method in Swift 5. Brutal. – SmileBot Jul 17 '20 at 21:28
  • Thank you, you helped me. They changed the method names but did not mentioned that old ones are deprecated, good job Apple. – Tornike Gomareli Jan 24 '21 at 19:58
16

Details

  • Xcode 9.2, Swift 4
  • Xcode 10.2.1 (10E1001), Swift 5

Solution

extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate {

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

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        print("\(info)")
        if let image = info[.originalImage] as? UIImage {
            imageView?.image = image
            dismiss(animated: true, completion: nil)
        }
    }
}

Usage

let imagePickerController = UIImagePickerController()
imagePickerController.allowsEditing = false
imagePickerController.sourceType = .photoLibrary
imagePickerController.delegate = self
present(imagePickerController, animated: true, completion: nil)

Full sample

Do not forget to add the solution code here (look above)

import UIKit

class ViewController: UIViewController {

    private weak var imageView: UIImageView?

    override func viewDidLoad() {
        super.viewDidLoad()
        let stackView = UIStackView(frame: .zero)
        stackView.axis = .vertical
        stackView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(stackView)
        stackView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true
        stackView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true

        let imageView = UIImageView()
        imageView.contentMode = .scaleAspectFit
        imageView.translatesAutoresizingMaskIntoConstraints = false
        stackView.addArrangedSubview(imageView)
        imageView.widthAnchor.constraint(equalToConstant: 200).isActive = true
        imageView.heightAnchor.constraint(equalToConstant: 200).isActive = true
        self.imageView = imageView

        let button = UIButton(frame: .zero)
        button.setTitle("Button", for: .normal)
        button.setTitleColor(.blue, for: .normal)
        button.addTarget(self, action: #selector(showImages), for: .touchUpInside)
        stackView.addArrangedSubview(button)
    }

    @IBAction func showImages(_ sender: AnyObject) {
        let imagePickerController = UIImagePickerController()
        imagePickerController.allowsEditing = false
        imagePickerController.sourceType = .photoLibrary
        imagePickerController.delegate = self
        present(imagePickerController, animated: true, completion: nil)
    }
}
Vasily Bodnarchuk
  • 24,482
  • 9
  • 132
  • 127
  • 9
    Please don't just post a whole bunch of code. A good answer explains what was wrong in the OP's code and how their answer solves the problem. – rmaddy Sep 25 '16 at 05:38
  • 1
    Ok. But in this question we have lot's of moments to explain. My English is not perfect, so, I decided to create full sample. Next time, I will try your suggest. Thank you! – Vasily Bodnarchuk Sep 25 '16 at 05:43
  • Also, @VasilyBodnarchuk, I know that placing the delegate and its methods inside of my calling view controller will work. What I am trying to do is create a helper class to act as the delegate because I have 3-4 different view controllers that need to use the exact same code, and I'd rather not repeat the same two delegate calls each time I switch view controllers. – Clayton C. Sep 25 '16 at 19:25
  • This still does not subclass the UIImagePickerController/UIImagePickerDelegate. What I want to do is make a single subclass, extension or file to refer to that can handle the call regardless of the view controller I'm calling it from – Clayton C. Sep 27 '16 at 18:12
7

As per my experience, it is an issue of ARC.

If we define instance as locally then ARC will remove its reference automatically once methods scope end. If you define globally then it is kept in memory until the view controller is not deinitialized.

Short Answer:

Define UIImagePickerController instance globally.

Long Answer:

I have created once the common class of NSObject and delegates method of UIImagePickerController is not called.

After 5 hours of brainstorming, Finally, get the solution. It seems like an issue related to memory deallocation during the captured image from the camera.

public typealias CameraBlock = (UIImage?, Bool) -> Void

class HSCameraSingleton: NSObject {
    
    var pickerController = UIImagePickerController()
    private var completionBlock: CameraBlock!
    var presentationController: UIViewController?
    
    public init(presentationController: UIViewController) {
        super.init()
        self.presentationController = presentationController
    }
    
    
    public func present(completionBlock: @escaping CameraBlock) {
        guard UIImagePickerController.isSourceTypeAvailable(.camera) else {
            return
        }
        
        self.pickerController = UIImagePickerController()
        self.pickerController.delegate = self
        self.pickerController.allowsEditing = true
        self.pickerController.sourceType = .camera
        self.completionBlock = completionBlock
        self.presentationController?.present(self.pickerController, animated: true, completion: nil)
    }
}


extension HSCameraSingleton: UIImagePickerControllerDelegate,UINavigationControllerDelegate {
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        self.completionBlock?(nil,false)
    }
    
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        guard let image = info[.originalImage] as? UIImage else {
            self.completionBlock?(nil,false)
            return
        }
        self.completionBlock?(image,true)
        pickerController.dismiss(animated:true, completion: nil)
    }
}


class AuthViewController: UIViewController{

lazy var overlay = HSCameraSingleton(presentationController:self)

@IBAction func actionLoginTap(_ sender: UIControl) {
        
        overlay.present { (image, status) in
            print(image,status)
        }
    }
}
Community
  • 1
  • 1
Hitesh Surani
  • 12,733
  • 6
  • 54
  • 65
6

I also faced this issue and solved it by using below solution. Set picker's delegate after present completion.

controller.present(picker, animated: true, completion: {
            self.picker.delegate = self
        })

Hope this will work for you!!

nitin.agam
  • 1,949
  • 1
  • 17
  • 24
5

I found that the delegate code had to be within an active UIViewController.

I originally tried to have my code in a separate file, as as NSObject with the correct delegate protocols declared, like this:

class PhotoPicker: NSObject, UINavigationControllerDelegate, UIImagePickerControllerDelegate {

But that never called the delegate methods.

Taking the exact same code and placing it within the UIViewController I was calling it from made it work.

It looks like the best solution is to create a pop-up type view, and have its ViewController keep the code.

Nico teWinkel
  • 870
  • 11
  • 19
5

Yo have to make sure that UIImagePickerController was not released before delegate called.

I created an ImagePicker class to handle all the UIImagePickerController stuff.

I created similar class, but

func onButtonDidTap(sender: UIButton) {
.....
    let picker = VLImagePickerController()
    picker.show(fromSender: sender, handler: { (image: UIImage?) -> (Void) in
        if (image != nil) {
            self.setImage(image!)
        }
    })
....
}

did not work for me. 'picker' was released before 'handler' could be called.

I created permanent reference, and it worked:

let picker = VLImagePickerController()

    func onButtonDidTap(sender: UIButton) {
    .....
            //let picker = VLImagePickerController()
            picker.show(fromSender: sender, handler: { (image: UIImage?) -> (Void) in
                if (image != nil) {
                    self.setImage(image!)
                }
            })
    ....
        }
Victor_Z
  • 121
  • 1
  • 3
  • 5
  • 1
    This happened for me...dont keep imagepicker as weak or an instance variable in a function...its best to be added as a property – anoop4real Jan 16 '20 at 22:21
4

swift 4.2

Add Delegate method according ViewController

UIImagePickerControllerDelegate,UINavigationControllerDelegate

//IBOutlet
@IBOutlet weak var ImagePhoto: UIImageView!

override func viewDidLoad() {
    super.viewDidLoad()
}

//Button Action Take Photo
@IBAction func btnPhotoTap(_ sender: Any) {
   
           let imagePicker = UIImagePickerController()
           imagePicker.delegate = self
           imagePicker.sourceType = .photoLibrary // Or .camera as you require
           imagePicker.allowsEditing = true
           self.present(imagePicker, animated: true, completion: nil)
}

//MARK:-imagePickerControllerDelegate
func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
           let image1 =  info[UIImagePickerController.InfoKey.editedImage] as? UIImage
           self.ImagePhoto.image = image1
           self.dismiss(animated: true, completion: nil)
}
func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
   
           print("Cancel")
           self.dismiss(animated: true, completion: nil)
}
Pravin Parmar
  • 149
  • 1
  • 6
2

This code works, (although, it redisplays over and over because it displays the picker in viewWillAppear, this is just to keep code small). I would look at what is different from this. It could have to do with your top view controller? Why not just display the picker from a view controller rather than go to application's top view controller? Also, once you get the delegate callback, you need to dismiss the view controller.

import UIKit
import MobileCoreServices

class ViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate {
    let imagePicker = UIImagePickerController()

    override func viewDidLoad() {
        super.viewDidLoad()
        imagePicker.mediaTypes = [kUTTypeImage as String]
        imagePicker.delegate = self
    }

    override func viewDidAppear(_ animated: Bool) { // keeps reopening, do not this in your code. 
        present(imagePicker, animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any]) {
        imagePicker.dismiss(animated: true, completion: nil)
    }
}
possen
  • 8,596
  • 2
  • 39
  • 48
1

I voted this one up because I was missing the UINavgationControllerDelegate declaration and this comment helped.

imagePickerController wasn't being called.

Ru Chern Chong
  • 3,692
  • 13
  • 33
  • 43
1

Something I found that helped me was making sure the delegate was set as public rather than private.

public func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [String : Any])
Ben Runger
  • 11
  • 1