2

I began to learn Swift recently. When I tried to make my first App I got confused with UIBarButtonItem. If I put let UIBarButtonItem initialization outside the viewDidLoad() function, nothing happens when I press the Next Button.

class ViewController: UIViewController, UITextFieldDelegate {
    let rightBarButton: UIBarButtonItem = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))

    override func viewDidLoad() {
        super.viewDidLoad()
        self.view.backgroundColor = .white

        self.navigationItem.rightBarButtonItem = rightBarButton
    }

    func onClickNext(button: UIBarButtonItem) {
        print("should push view controller")
    }
}

However, when I put the initialization into the viewDidLoad() function, the output area does output the sentense that I set in the onClickNext(button:) function.

class ViewController: UIViewController, UITextFieldDelegate {
    var rightBarButton: UIBarButtonItem?

    override func viewDidLoad() {
        super.viewDidLoad()

        self.view.backgroundColor = .white

        self.rightBarButton = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))
        self.navigationItem.rightBarButtonItem = rightBarButton
    }

    func onClickNext(button: UIBarButtonItem) {
        print("should push view controller")
    }
}

Also, I I found that when I put the initialization outside the viewDidLoad() function, and I add a UITextField to viewController, the rightBarButton works if I touch the textfield before I press the button.

That make me confused. What is the mechanism?

Rohit Poudel
  • 1,793
  • 2
  • 20
  • 24
Leadrains
  • 23
  • 4

4 Answers4

1

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:)))
}
Sophy Swicz
  • 1,307
  • 11
  • 21
  • 1
    Thanks! That solves my first question. Now I'm curious about the second question: why it works if I touch the textfield before I press the button? – Leadrains Jul 31 '17 at 08:03
  • @user5685969 said it maybe a bug refer to the question:https://stackoverflow.com/questions/43842928/ios-lazy-var-uibarbuttonitem-target-issue – Leadrains Jul 31 '17 at 08:07
  • Are you adding the UITtexField without setting its delegate? how are you adding this control by code or by xib? It could be a bug, but I need more info – Sophy Swicz Jul 31 '17 at 08:43
  • I just add UITextField without setting its delegate by code. And then I find that: if I put the UIBarButtonItem initialization outside the viewDidLoad() function, the target action never works, but after I touch the textfield, it works unexpectedly. – Leadrains Jul 31 '17 at 11:06
  • And I don't think it has matter with textfield's delegate. I can's explain how iOS run to make the target action work again by pressing the textfield. – Leadrains Jul 31 '17 at 11:19
  • I believe that it's possible that when you trigger any event of elements that are set when self exist, for some reason sets the rest of the elements that uses self (target of uibutton). I need to do some test, but, you can try using breakpoints! try to run the code and put it inside ViewDidLoad, in console log using po rightBarButton and see what does it have as properties (if target is set) and put other in UITextFields delegate and see if rightBarButton changes – Sophy Swicz Jul 31 '17 at 12:36
0
import UIKit


    class ViewController: UIViewController {

        var  btnName = UIButton()



        override func viewDidLoad() {
            super.viewDidLoad()
            btnName.setImage(UIImage(named: "imagename"), for: .normal)
            btnName.frame = CGRect(x:0,y: 0,width: 30,height: 30)
            btnName.addTarget(self, action: #selector(addTargetActionForButton(:_)), for: .touchUpInside)

            let rightBarButton = UIBarButtonItem(customView: btnName)
            self.navigationItem.rightBarButtonItem = rightBarButton

        }

        func addTargetActionForButton(_ sender : UIButton){


        }
    }
Ramesh.G
  • 359
  • 1
  • 10
  • Thanks. But I know how to make it work, I just confuse that why it doesn't work when I put the initialization outside the viewDidLoad() function. – Leadrains Jul 31 '17 at 06:35
0

Your are taking let variable out side of viewDidLoad You can not take let global variable. if you want to take let than you need to declare inside viewDidLoad. Fore more info let and var check this link What is the difference between `let` and `var` in swift?

 override func viewDidLoad() {

  let rightBarButton: UIBarButtonItem  = UIBarButtonItem(title: "Next", style: .plain, target: self, action: #selector(onClickNext(button:)))

    super.viewDidLoad()
    self.view.backgroundColor = .white

    self.navigationItem.rightBarButtonItem = rightBarButton


}
Lalit kumar
  • 1,797
  • 1
  • 8
  • 14
0

UIBatButtonItem may contain UIButton

enter image description here above example image have UINavigationItem which contains LeftBarButtonItems ([UIBarButton]), and RightBarButtonItems([UIBarButtonItem]), each UIBarButtonItem contain UIButton

we can customise the UIButton to specify how to display in View And we can connect button action directly to the UIButton

Berlin Raj
  • 124
  • 6