Uibarbuttonitem Doesn't Work When Created as a Property, But Does When Created in a Function

UIBarButtonItem doesn't work when created as a property, but does when created in a function

As you've discovered, it makes a big difference where this line occurs:

let barButton = UIBarButtonItem(image: #imageLiteral(resourceName: "settings"), 
style: .plain, target: self, action: #selector(presentSettings))

The problem is the target:self part. When the bar button item is configured as part of an instance property initializer (your first example), the instance doesn't exist yet — it is what we are initializing. So self has no meaning, and the button ends up with no target. Therefore, tapping the button does nothing.

(Actually, to be quite technical, self is the class, but that's not a helpful thing to know.)

In your second example, that line is part of viewDidLoad, which runs considerably after the view controller instance has come into existence and has been initialized. viewDidLoad is an instance method, in fact. So self is the instance, as you expect.

UIBarButtonItem is not working swift 3.0

Try this:

override func viewDidLoad() {
super.viewDidLoad()

self.navigationItem.rightBarButtonItem = UIBarButtonItem(title: "ButtonName", style: .done, target: self, action: #selector(YourViewController.yourAction))

}

swift 3.0 UIBarButtonItem action not work


Swift 3.0

Declare UIBarButton inside ViewDidLoad()

override func viewDidLoad() {
super.viewDidLoad()
let logout: UIBarButtonItem = UIBarButtonItem.init(title: "Logout", style: .plain, target: self, action: #selector(ViewController.logOut))
}

func logOut() {
print("LogOut")
}

Declare UIBarButtonItem OutSide ViewDidLoad()

var logout:UIBarButtonItem = UIBarButtonItem()

override func viewDidLoad() {
super.viewDidLoad()
logout = UIBarButtonItem.init(title: "Logout", style: .plain, target: self, action: #selector(ViewController.logOut))
}

func logOut() {
print("LogOut")
}

Declare Completely Outside viewDidLoad()

lazy var logout: UIBarButtonItem = {
UIBarButtonItem.init(title: "Logout", style: .plain, target: self, action: #selector(ViewController.logOut))
}()

Any Should work.

For your action parameter either you can specify ViewController name explicitly or you can just say self.

action: #selector(self.logOut)

SWIFT - UIBarButtonItem is not calling action

The problem is that uploadButton is getting initialized too early, i.e. during the initialization of the view controller ifself. At that point, self is not yet ready for use.

There are a few ways to solve this.

  1. Initialize the button in viewDidLoad:
class MyViewController: UIViewController {

override func viewDidLoad() {
super.viewDidLoad()
let uploadButton = UIBarButtonItem(title: "UPLOAD", style: .plain, target: self, action: #selector(uploadHandler))
self.navigationItem.setRightBarButton(uploadButton, animated: false)
}
...
}

  1. If some other methods in your class need access to it, slightly modify the code above by creating an implicitly unwrapped stored property and setting it in viewDidLoad:
class MyViewController: UIViewController {

private var uploadButton: UIBarButtonItem!

override func viewDidLoad() {
super.viewDidLoad()
uploadButton = UIBarButtonItem(title: "UPLOAD", style: .plain, target: self, action: #selector(uploadHandler))
self.navigationItem.setRightBarButton(uploadButton, animated: false)
}
...
}

  1. Make the initialization of the button lazy, that way it will be initialized when it's first accessed, i.e. in viewDidLoad:
class MyViewController: UIViewController {

private lazy var uploadButton = UIBarButtonItem(title: "UPLOAD", style: .plain, target: self, action: #selector(uploadHandler))

override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.setRightBarButton(uploadButton, animated: false)
}
...
}

  1. Initialize the button without an action and a selector, add them later in viewDidLoad:
class MyViewController: UIViewController {

private let uploadButton = UIBarButtonItem(title: "UPLOAD", style: .plain, target: nil, action: nil)

override func viewDidLoad() {
super.viewDidLoad()
uploadButton.target = self
uploadButton.action = #selector(uploadHandler)
self.navigationItem.setRightBarButton(uploadButton, animated: false)
}
...
}

There's very little difference among all these options, so it should be fine whatever one you choose.

A couple of side notes:

  • In my examples I declared uploadButton as private to indicate that it is not a part of your view controller's public API. If you need to access this button outside of your view controller, just remove the private modificator.
  • Animation doesn't get performed in viewDidLoad because your view is not yet on screen, so calling setRightBarButton(uploadButton, animated: true) will not trigger the animation, therefore I replaced true with false. If you want the user to see the animation, call it in viewDidAppear.

Why adding barBarttonItem won’t work if created out of scope where it’s added?

These bar button properties are created before init is called and you're trying to access class methods which can't be called before init is called.

If you want to initialize your buttons on class scope, you will have to make them lazy variables in order to create them when they are needed

lazy var rightButton = UIBarButtonItem(title: "Right", style: .plain, target: self, action: #selector(onRightClick))
lazy var leftButton = UIBarButtonItem(title: "Left", style: .plain, target: self, action: #selector(onLeftClick))

Swift, confusion with UIBarButtonItem

Well, maybe you are missing how a ViewController works inside.

First, viewDidLoad is the area were you usually setup or initialize any view or properties of the view. This method is also called only once during the life of the view controller object. This means that self already exists.

Knowing this, is important to understand what a let property does, (from Apple)

A constant declaration defines an immutable binding between the constant name and the value of the initializer expression; after the value of a constant is set, it cannot be changed. That said, if a constant is initialized with a class object, the object itself can change, but the binding between the constant name and the object it refers to can’t.

Even though the upper area is where you declare variables and constants, is usually meant for simple initialization, it's an area for just telling the VC that there is an object that you want to work with and will have a class global scope, but the rest of functionality will be added when the view hierarchy gets loaded (means that the object does not depends of self, for example, when adding target to a button, you are referring to a method inside of self)....this variables or constants are called Stored Properties

In its simplest form, a stored property is a constant or variable that is stored as part of an instance of a particular class or structure. Stored properties can be either variable stored properties (introduced by the var keyword) or constant stored properties (introduced by the let keyword).

And finally, you have a lazy stored property that maybe can be applied for what you want:

A lazy stored property is a property whose initial value is not calculated until the first time it is used. You indicate a lazy stored property by writing the lazy modifier before its declaration.

Solution: create a lazy var stored property or add his properties inside ViewDidLoad (when self already exists)

lazy private var doneButtonItem : UIBarButtonItem = {
[unowned self] in
return UIBarButtonItem(title: "Next", style:UIBarButtonItemStyle.Plain, target: self, action: #selector(onClickNext(button:)))
}()

OR

let rightBarButton: UIBarButtonItem?

override func viewDidLoad() {
super.viewDidLoad()
rightBarButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
}

Property isEnabled of UIBarButtonItem is an unrecognized selector in an extension of the class

Extensions are not supposed to be used to override any existing functionality. Extensions are only supposed to be used to add new functionality.

For the Extensions chapter in the Swift book:

“Extensions can add new functionality to a type, but they cannot override existing functionality.”

So the proper solution is to subclass UIBarButtonItem if you wish to override existing functionality. Then use the subclass wherever you need it.

UIBarButtonItem created with UIButton not showing up in UIToolbar

I figured it out! Like I was hoping, it was something stupid. I was setting up the button directly in viewDidLoad, and the toolbar in a helper function. I had thought that I was calling the helper function before declaring the button, but it turns out I was calling it before the button even got declared... Lesson learned: use breakpoints and the Xcode debugger more.



Related Topics



Leave a reply



Submit