Get UIView from UIBarButtonItem using iOS 11 return nil

Previously to iOS 11, I get a reference to the view of a UIBarButtonItem as follow:


let addBarButtomItemView: UIView = addBarButtomItem.value(forKey: "view") as! UIView



No with iOS 11, I get this error:


fatal error: unexpectedly found nil while unwrapping an Optional value


How could reference the view of a UIBarButtonItem using iOS 11?

Accepted Reply

You are right, I was using a private APIs, and it will not pass the apple app store review process.


What I was looking for was a way to hide the UIBarButtonItem under some circustances.


Wrong code:


import UIKit

struct User{
    let name : String
}

class WrongViewController: UIViewController {
    @IBOutlet weak var addBarButtomItem: UIBarButtonItem!

    @IBAction func add(_ sender: Any) {
    }

    @IBAction func createUser(_ sender: Any) {
        user = User(name: "John")
    }

    var user : User?{
        didSet{
             //  iOS 11 fatal error: unexpectedly found nil while unwrapping an Optional value
            let addBarButtomItemView: UIView = addBarButtomItem.value(forKey: "view") as! UIView

            if user != nil {
                addBarButtomItem.isEnabled = true
                addBarButtomItemView.isHidden = false
            } else {
                addBarButtomItem.isEnabled = false
                addBarButtomItemView.isHidden = true
            }
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
         user = nil
    }
}


I finally solve the issue using an strong IBOutlet, instead of weak, and placing it in the ViewController instead of in the Navigation Item and adding or removing it to the Navigation Item instead of hidding it.


Right code:


import UIKit

struct User{
    let name : String
}

class RightViewController: UIViewController {
    @IBOutlet var addBarButtomItem: UIBarButtonItem!

    @IBAction func add(_ sender: Any) {

    }

    @IBAction func createUser(_ sender: Any) {
          user = User(name: "John")
    }

    var user : User?{
        didSet{
            if user != nil {
                navigationItem.rightBarButtonItem = addBarButtomItem
            } else {
                navigationItem.rightBarButtonItem = nil
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
         user = nil
    }
}


I found also another way using a weak IBOutlet, instead of get a reference to the view of the UIBarButtonItem and hide it, using the tintColor property of the UIBarButtonItem, as follow:


import UIKit
struct User{
    let name : String
}
class RightViewController: UIViewController {
    @IBOutlet weak var addBarButtomItem: UIBarButtonItem!

    @IBAction func add(_ sender: Any) {
    }

    @IBAction func createUser(_ sender: Any) {
        user = User(name: "John")
    }

    var user : User?{
        didSet{
            if user != nil {
                addBarButtomItem.isEnabled = true
                addBarButtomItem.tintColor =  nil // set a UIBarbutton back to the default tint color
            } else {
                addBarButtomItem.isEnabled = false
                addBarButtomItem.tintColor = .clear
            }
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        user = nil
    }
}

Replies

Looks like you're trying to get at private data. That's a bad idea.


If you've made a bar button item with a custom view and need that view later, just keep a reference to that custom view.


If this is not a custom view situation, what are you trying to do with the bar button item?

I am running into a similar issue.


My goal is to get the image of the action icon UIBarButtonSystemItem.action


Previously, it was possible to create a UIToolbar, attach the UIBarButton to it, sift through the subviews for a UIButton and grab the image of that button.


This is no longer possible in Xcode 9/iOS 11 as far as I can tell.

You are right, I was using a private APIs, and it will not pass the apple app store review process.


What I was looking for was a way to hide the UIBarButtonItem under some circustances.


Wrong code:


import UIKit

struct User{
    let name : String
}

class WrongViewController: UIViewController {
    @IBOutlet weak var addBarButtomItem: UIBarButtonItem!

    @IBAction func add(_ sender: Any) {
    }

    @IBAction func createUser(_ sender: Any) {
        user = User(name: "John")
    }

    var user : User?{
        didSet{
             //  iOS 11 fatal error: unexpectedly found nil while unwrapping an Optional value
            let addBarButtomItemView: UIView = addBarButtomItem.value(forKey: "view") as! UIView

            if user != nil {
                addBarButtomItem.isEnabled = true
                addBarButtomItemView.isHidden = false
            } else {
                addBarButtomItem.isEnabled = false
                addBarButtomItemView.isHidden = true
            }
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
         user = nil
    }
}


I finally solve the issue using an strong IBOutlet, instead of weak, and placing it in the ViewController instead of in the Navigation Item and adding or removing it to the Navigation Item instead of hidding it.


Right code:


import UIKit

struct User{
    let name : String
}

class RightViewController: UIViewController {
    @IBOutlet var addBarButtomItem: UIBarButtonItem!

    @IBAction func add(_ sender: Any) {

    }

    @IBAction func createUser(_ sender: Any) {
          user = User(name: "John")
    }

    var user : User?{
        didSet{
            if user != nil {
                navigationItem.rightBarButtonItem = addBarButtomItem
            } else {
                navigationItem.rightBarButtonItem = nil
            }
        }
    }

    override func viewDidLoad() {
        super.viewDidLoad()
         user = nil
    }
}


I found also another way using a weak IBOutlet, instead of get a reference to the view of the UIBarButtonItem and hide it, using the tintColor property of the UIBarButtonItem, as follow:


import UIKit
struct User{
    let name : String
}
class RightViewController: UIViewController {
    @IBOutlet weak var addBarButtomItem: UIBarButtonItem!

    @IBAction func add(_ sender: Any) {
    }

    @IBAction func createUser(_ sender: Any) {
        user = User(name: "John")
    }

    var user : User?{
        didSet{
            if user != nil {
                addBarButtomItem.isEnabled = true
                addBarButtomItem.tintColor =  nil // set a UIBarbutton back to the default tint color
            } else {
                addBarButtomItem.isEnabled = false
                addBarButtomItem.tintColor = .clear
            }
        }
    }
    override func viewDidLoad() {
        super.viewDidLoad()
        user = nil
    }
}

I don't know if you can use Apple images in your apps, but if so, you can grab the image from:


https://developer.apple.com/documentation/uikit/uibarbuttonsystemitem/1617116-action


And add it to your project.