2

I have a fairly simply NSObject class that is what I am calling a MediaPicker. This class asks the user what type of media they want to pick and subsequently presents an UIImagePickerController or a UIDocumentPickerViewController based on their selection.

The problem I'm facing is that the UIImagePickerControllerDelegate and the UIDocumentPickerViewControllerDelegate methods are not being called.

My class is below:

import Foundation
import UIKit
import MobileCoreServices

public protocol MediaPickerDelegate: NSObjectProtocol {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any])
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController)
    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController)
    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL])
}

class MediaPicker: NSObject {
    //Data
    var viewController: UIViewController? = nil
    var imagePicker: UIImagePickerController = UIImagePickerController()
    let documentPicker = UIDocumentPickerViewController(documentTypes: [(kUTTypeImage as String), (kUTTypeMovie as String)], in: .import)

    //Delegate
    weak var delegate: MediaPickerDelegate? = nil

    override init() {
        super.init()
    }

    public func showMediaPicker(from presentingViewController: UIViewController) {
        //Set Data
        self.viewController = presentingViewController

        //Create Alert
        let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .actionSheet)

        //Add Library Action
        let libraryAction: UIAlertAction = UIAlertAction(title: "Photo & Video Library", style: .default) { (action) in
            self.presentLibraryPicker()
        }
        libraryAction.setValue(UIImage(named: "gallery_picker_library"), forKey: "image")
        libraryAction.setValue(CATextLayerAlignmentMode.left, forKey: "titleTextAlignment")
        alertController.addAction(libraryAction)

        //Add Cloud Action
        let cloudAction: UIAlertAction = UIAlertAction(title: "Other Locations", style: .default) { (action) in
            self.presentDocumentPicker()
        }
        cloudAction.setValue(UIImage(named: "gallery_picker_cloud"), forKey: "image")
        cloudAction.setValue(CATextLayerAlignmentMode.left, forKey: "titleTextAlignment")
        alertController.addAction(cloudAction)

        //Add Cancel Action
        let cancelAction: UIAlertAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
        alertController.addAction(cancelAction)

        //Show Alert
        self.viewController?.present(alertController, animated: true, completion: nil)
    }

    private func presentLibraryPicker() {
        imagePicker.delegate = self
        imagePicker.sourceType = .photoLibrary
        imagePicker.mediaTypes = UIImagePickerController.availableMediaTypes(for: .savedPhotosAlbum) ?? []
        imagePicker.allowsEditing = true
        self.viewController?.present(imagePicker, animated: true, completion: nil)
    }

    private func presentDocumentPicker() {
        documentPicker.delegate = self
        documentPicker.allowsMultipleSelection = false
        self.viewController?.present(documentPicker, animated: true, completion: nil)
    }
}

extension MediaPicker: UIImagePickerControllerDelegate {
    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey: Any]) {
        delegate?.imagePickerController(picker, didFinishPickingMediaWithInfo: info)
    }

    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        delegate?.imagePickerControllerDidCancel(picker)
    }
}

extension MediaPicker: UIDocumentPickerDelegate {
    func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) {
        delegate?.documentPickerWasCancelled(controller)
    }

    func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) {
        delegate?.documentPicker(controller, didPickDocumentsAt: urls)
    }
}

extension MediaPicker: UINavigationControllerDelegate {
    //Redundant delegate implented as part of UIImagePickerControllerDelegate
}

The class calling this looks like this:

class Foo: UIViewController {

    @objc func openPicker() {
        let mediaPicker: MediaPicker = MediaPicker()
        mediaPicker.delegate = self
        mediaPicker.showMediaPicker(from: self)
    }
}

I've tried making the protocol for MediaPickerDelegate both a class and NSObjectProtocol but with no luck. I also tried (thinking maybe an ARC issue from my obj-c days) making the two picker variables global and local in scope to the function but this didn't seem to change anything.

I'm also open to any feedback you have on the quality of the code that's been written/best practices that I may have missed.

Brandon Stillitano
  • 1,304
  • 8
  • 27
  • somehow your imagepicker's delegate goes nil when it pops up. try subclassing UIImagePickerController, and open use that instead UIImagePickerController. you will get what I want to say. – Rahul Patel Feb 06 '20 at 06:05
  • I understand that this is the issue however I don't want to subclass a UIViewController. If I subclass either of these classes then I lose the ability to interact with the other class. This won't work for this use case. – Brandon Stillitano Feb 06 '20 at 10:25

2 Answers2

3

Ok, so I've done some testing/tweaking and have realised that both of the document/image picker controllers are being removed from memory as soon as they're displayed. This is because they are local to the scope of the function that is calling them and the function has finished executing.

What I mean here is that in the function openPicker (from the question) the class that is calling it is doing some ARC magic and removing it as soon as it thinks the function is finished being called. Because the mediaPicker is local to openPicker and not global to class Foo it doesn't understand that it needs to keep it in memory.

The solution here is to have a global variable at a class level inside class Foo and access that inside of the function openPicker instead of initialising a new instance of MediaPicker as follows:

class Foo: UIViewController {
    //Data
    var mediaPicker: MediaPicker = MediaPicker()

    @objc func openPicker() {
        self.mediaPicker.delegate = self
        self.mediaPicker.showMediaPicker(from: self)
    }
}

Once I migrated to using this approach, the delegates are being called and the debugger clearly shows that the object is being retained in memory whilst the class/UIViewController is still interactively available to the user.

Brandon Stillitano
  • 1,304
  • 8
  • 27
-3

Have try to get permission for Photo Library(Privacy - Photo Library Usage Description) in Info.plist?

OKMIN
  • 97
  • 5