Customising UIBarButtons of UIImagePickerController

I am trying to add a graphic button in the navigation bar of an UIIMagePickerController with little luck. This is my class in which I tried to set it nearly everywhere with no success:

import UIKit
class PickerViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
    var pickImageCallback : ((UIImage?) -> ())?
    var cancelImageCallback : (() -> Void)?
    var cameraView: UIView!
    var cameraButton:UIButton!
    var picker = UIImagePickerController()
    override func viewDidLoad() {
        super.viewDidLoad()
        picker.delegate=self
        picker.sourceType = .photoLibrary
        picker.navigationBar.isTranslucent = false
        picker.navigationBar.barTintColor = .blue // Background color
        picker.navigationBar.tintColor = .white // Cancel button ~ any UITabBarButton items
        picker.navigationBar.titleTextAttributes = [
                NSAttributedString.Key.foregroundColor: UIColor.white
        ]
        self.present(picker, animated: true, completion: nil)
        //self.add(asChildViewController: picker)
    }
    
    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        print("m in")
        self.navigationItem.leftBarButtonItem=UIBarButtonItem(image: UIImage(named:"camera"), style: .plain, target: self, action: #selector(openCamera))
        navigationController.navigationBar.tintColor=UIColor.black
        navigationController.navigationBar.barTintColor=UIColor.red
       
    }
    
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        self.navigationItem.rightBarButtonItem=UIBarButtonItem(image: UIImage(named:"camera"), style: .plain, target: self, action: #selector(openCamera))
        //self.cameraView=UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 40))
        //self.cameraView.backgroundColor=UIColor.clear
        //cameraButton=UIButton(frame: CGRect(x: 60, y: 12, width: 50, height: 35))
        //cameraButton.setBackgroundImage(UIImage(named:"camera"), for: .normal)
        //cameraButton.reversesTitleShadowWhenHighlighted=true
        //cameraButton.addTarget(self, action: #selector(openCamera), for: .touchUpInside)
        //self.cameraView.addSubview(cameraButton)
        //self.view.addSubview(cameraView)
    }
    
    private func add(asChildViewController viewController: UIViewController) {
        // Add Child View Controller
        addChild(viewController)

        // Add Child View as Subview
        view.addSubview(viewController.view)

        // Configure Child View
        viewController.view.frame = view.bounds
        viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        // Notify Child View Controller
        viewController.didMove(toParent: self)
        viewController.navigationItem.leftBarButtonItem=UIBarButtonItem(image: UIImage(named:"camera"), style: .plain, target: self, action: #selector(openCamera))
    }
    
    @objc func openCamera(picker: UIImagePickerController){
        if(UIImagePickerController.isSourceTypeAvailable(.camera)){
            let picker=UIImagePickerController()
            picker.delegate=self
            picker.sourceType = .camera
            picker.showsCameraControls=true
            self.add(asChildViewController: picker)
        } else {
            let title=NSLocalizedString("Warning", comment:"")
            let message=NSLocalizedString("You don't have camera", comment:"")
            let dismiss=NSLocalizedString("Dismiss", comment:"")
            let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
            let cancelAction = UIAlertAction(title: dismiss, style: .cancel) { (action) in
            }
            alertController.addAction(cancelAction)
            if let viewController=UIApplication.topViewController() as? ViewController{
                viewController.proxyPresentViewController(alertController, animated: true, completion: nil)
            }
        }
    }
    
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        cancelImageCallback?()
        picker.dismiss(animated: true, completion: nil)
        self.dismiss(animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        self.dismiss(animated: true, completion: nil)
        var finalImage: UIImage?
        if let image = info[.livePhoto] as? UIImage {
            finalImage=image
        } else if let image = info[.editedImage] as? UIImage {
            finalImage=image
        } else if let image = info[.originalImage] as? UIImage {
            finalImage=image
        }
        picker.dismiss(animated: true, completion: nil)
        dismiss(animated: true, completion: nil)
        pickImageCallback?(finalImage)
    }
}
      }
        picker.dismiss(animated: true, completion: nil)
        dismiss(animated: true, completion: nil)
        pickImageCallback?(finalImage)
    }
}

Replies

Did you read this:

https://stackoverflow.com/questions/20420569/add-a-back-button-to-uiimagepicker-controller-navigation-nar


It's objc, but easy to port to Swift.


You've probably used this code:

https://stackoverflow.com/questions/35122588/adding-left-uibarbutton-in-uiimagepickercontroller

func navigationController(navigationController: UINavigationController, willShowViewController viewController: UIViewController, animated: Bool) {
     print("m in")
    viewController.navigationItem.title = "Home"
    let camera = UIBarButtonItem(title: "camera", style: .Plain, target:self, action:  "btnOpenCamera")
    viewController.navigationItem.leftBarButtonItem = camera



Your code is a bit different, using self, instead of viewController, which is the parameter passed to the func.

    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        print("m in")
        self.navigationItem.leftBarButtonItem=UIBarButtonItem(image: UIImage(named:"camera"), style: .plain, target: self, action: #selector(openCamera))
        navigationController.navigationBar.tintColor=UIColor.black
        navigationController.navigationBar.barTintColor=UIColor.red
    }

As you may see, in my code I had:


func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool)

changing it to:


func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool)

changes absolutely nothing.


At any rate I tried virtually anything: NavigationController, ViewController, self, picker...

At any rate I changed the code to the following, given presenting a view controller over another view controller was not a great idea:


import UIKit
class PickerViewController: UIViewController, UIImagePickerControllerDelegate, UINavigationControllerDelegate{
    var pickImageCallback : ((UIImage?) -> ())?
    var cancelImageCallback : (() -> Void)?
    var cameraView: UIView!
    var cameraButton:UIButton!
    var picker: UIImagePickerController!
    override func viewDidLoad() {
        super.viewDidLoad()
        picker=UIImagePickerController()
        picker.delegate=self
        picker.sourceType = .photoLibrary
        self.add(asChildViewController: picker)
    }
   
    func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
        print("m in")
        viewController.navigationItem.title = "Home"
        let camera = UIBarButtonItem(title: "camera", style: .plain, target:self, action: #selector(openCamera))
        viewController.navigationItem.leftBarButtonItem = camera
    }
   
    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        //self.navigationItem.leftBarButtonItem=UIBarButtonItem(image: UIImage(named:"camera"), style: .plain, target: self, action: #selector(openCamera))
        //self.cameraView=UIView(frame: CGRect(x: 0, y: 0, width: self.view.frame.width, height: 40))
        //self.cameraView.backgroundColor=UIColor.clear
        //cameraButton=UIButton(frame: CGRect(x: 60, y: 12, width: 50, height: 35))
        //cameraButton.setBackgroundImage(UIImage(named:"camera"), for: .normal)
        //cameraButton.reversesTitleShadowWhenHighlighted=true
        //cameraButton.addTarget(self, action: #selector(openCamera), for: .touchUpInside)
        //self.cameraView.addSubview(cameraButton)
        //self.view.addSubview(cameraView)
    }
   
    private func add(asChildViewController viewController: UIViewController) {
        // Add Child View Controller
        addChild(viewController)

        // Add Child View as Subview
        view.addSubview(viewController.view)

        // Configure Child View
        viewController.view.frame = view.bounds
        viewController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]

        // Notify Child View Controller
        viewController.didMove(toParent: self)
        viewController.navigationItem.leftBarButtonItem=UIBarButtonItem(image: UIImage(named:"camera"), style: .plain, target: self, action: #selector(openCamera))
    }
   
    @objc func openCamera(){
        if(UIImagePickerController.isSourceTypeAvailable(.camera)){
            let picker=UIImagePickerController()
            picker.delegate=self
            picker.sourceType = .camera
            picker.showsCameraControls=true
            self.add(asChildViewController: picker)
        } else {
            let title=NSLocalizedString("Warning", comment:"")
            let message=NSLocalizedString("You don't have camera", comment:"")
            let dismiss=NSLocalizedString("Dismiss", comment:"")
            let alertController = UIAlertController(title: title, message: message, preferredStyle: .alert)
            let cancelAction = UIAlertAction(title: dismiss, style: .cancel) { (action) in
            }
            alertController.addAction(cancelAction)
            if let viewController=UIApplication.topViewController() as? ViewController{
                viewController.proxyPresentViewController(alertController, animated: true, completion: nil)
            }
        }
    }
   
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        cancelImageCallback?()
        self.dismiss(animated: true, completion: nil)
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        self.dismiss(animated: true, completion: nil)
        var finalImage: UIImage?
        if let image = info[.livePhoto] as? UIImage {
            finalImage=image
        } else if let image = info[.editedImage] as? UIImage {
            finalImage=image
        } else if let image = info[.originalImage] as? UIImage {
            finalImage=image
        }
        pickImageCallback?(finalImage)
        self.dismiss(animated: true, completion: nil)
    }
}

And, does it work so ?

Nope: I also tried to insert the picker in the main class by putting the callback in an extensione with exactly the same result: I both opened a bug report and a TSI. I hope someone shall take care:


extension ViewController: UIImagePickerControllerDelegate, UINavigationControllerDelegate{
    func imagePickerControllerDidCancel(_ picker: UIImagePickerController) {
        picker.dismiss(animated: true, completion:{
            self.maySubmit=true
        })
        cancelImageCallback?()
    }

    func imagePickerController(_ picker: UIImagePickerController, didFinishPickingMediaWithInfo info: [UIImagePickerController.InfoKey : Any]) {
        self.dismiss(animated: true, completion: nil)
        var finalImage: UIImage?
        if let image = info[.livePhoto] as? UIImage {
            finalImage=image
        } else if let image = info[.editedImage] as? UIImage {
            finalImage=image
        } else if let image = info[.originalImage] as? UIImage {
            finalImage=image
        }
        pickImageCallback?(finalImage)
        picker.dismiss(animated: true, completion: {
            self.maySubmit=true
        })
    }
       
    func navigationController(_ navigationController: UINavigationController, didShow viewController: UIViewController, animated: Bool) {
        print("navigation will show")
        let bar = navigationController.navigationBar
        bar.isHidden=false
        if let barItem=bar.topItem {
            let camera = UIBarButtonItem(title: "camera", style: .plain, target:self, action: #selector(openCamera))
            barItem.rightBarButtonItem=camera
            let dismiss = UIBarButtonItem(barButtonSystemItem: .cancel, target: self, action: #selector(cancel))
            barItem.leftBarButtonItem=dismiss
        }
    }
}

It seems that lines 20 and 49 are redundant.


line 20

        viewController.navigationItem.leftBarButtonItem = camera

line 49

        viewController.navigationItem.leftBarButtonItem=UIBarButtonItem(image: UIImage(named:"camera"), style: .plain, target: self, action: #selector(openCamera))


In addition, openCamera calls add function, before add connects openCamera as an action.

I made several tests: the issue is that neither the text nor the image are shown to bother for the callback function!

At any rate openCamera is in the main class, otherwise the compliler would not even compile.