Search code examples
iosswiftuiviewcontrolleruibarbuttonitemuistoryboardsegue

Why isn't my UIBarButtonItem calling my custom function?


I have a UIViewController with a UIBarButtonItem configured to call my custom function called func settingsTapped(sender: AnyObject?) in which I've put performSegueWithIdentifier. I've determined that this function isn't being called even though the button DOES work and the segue somehow still works because it goes to the correct view controller.

First VC:

class CalculatorViewController: UIViewController {

  private let timeInterval = 0.016 //how frequently we change the displayed number
  private let animationLength = 0.3
  private var timer: NSTimer?
  private var counter: Int = 0
  private var max: Double = 0.0
  var startingPreferredUnits: String?
  @IBOutlet weak var weightLifted: UITextField!
  @IBOutlet weak var repetitions: UITextField!
  @IBOutlet weak var oneRepMax: UILabel!
  @IBOutlet weak var percentages: UITextView!
  @IBOutlet weak var units: UILabel! 

  override func viewDidLoad() {

    let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(settingsTapped(_:)))

    self.navigationItem.leftBarButtonItem = settingsButton

    super.viewDidLoad()
  }

 func settingsTapped(sender: AnyObject?) {
    startingPreferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
    print("In segue, units is \(startingPreferredUnits)") // never prints this caveman debugging 
    self.performSegueWithIdentifier("segueToSettings", sender: self)
  }
}

In the storyboard, I placed a Bar Button Item in the nav bar:

enter image description here

I created a Show (e.g. Push) segue between the Settings bar button item and the Settings view controller and I've given this segue an identifier of 'segueToSettings`. When I touch the Settings button, it does present the Settings view controller, but it doesn't print my caveman debugging line to the console.

I also tried creating the segue between the CalculatorViewController itself and the SettingsViewController (which I think may even be a better way) but when I have it set up that way, nothing happens at all when I touch the Settings button.

I've tried everything I could find on SO but nothing has worked. I hope I don't earn a Stupid Question badge on this one.

UPDATE 1:

I'm still struggling with this one. Here's something else I've learned and tried that didn't work. Clicking the Settings button works by performing the segue from the button to the Settings page which I created in the Storyboard. As one would expect, it will call override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) before doing this. So it's completely ignoring the action: #selector(settingsTapped(_:)) part. Also, I can change the identifier of the segue in the storyboard and it makes no difference at all. It still works. I can even delete the identifier and it works.

I also tried adding another button (this time a barButtonSystemItem like so:

 override func viewDidLoad() {
    super.viewDidLoad()

    let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(settingsTapped(_:)))

let saveButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(self.saveThisLift(_:)))

    self.navigationItem.leftBarButtonItem = settingsButton
    self.navigationItem.rightBarButtonItem = saveButton
  }  


  func saveThisLift(sender: UIBarButtonItem) {
    print("I'm in saveThisLift") // never prints
    let weight = weightLifted.text
    let reps = repetitions.text
    let maxAmount = oneRepMax.text
    let unitsText = units.description

    coreDataStack.saveLiftEvent(currentLiftName!, formula: currentFormulaName!, weight: weight!, repetitions: reps!, maxAmount: maxAmount!, unitsUsed: unitsText)
}

 func settingsTapped(sender: AnyObject?) {
        startingPreferredUnits = UserDefaultsManager.sharedInstance.preferredUnits
        print("In segue, units is \(startingPreferredUnits)") // never prints this caveman debugging 
        self.performSegueWithIdentifier("segueToSettings", sender: self)
      }

Like the Settings button, touching it does nothing except make it flash. I should point out that I added the bar button items via the storyboard and I think that's unnecessary since I'm trying to add them programmatically. However, without them, neither of the buttons appears.


Solution

  • After a lot more research and more trial and error, I've figured it out. I learned a number of things that I'll leave here for anyone who has this problem in the future.

    At a high-level, trying to do some of this in the storyboard and some in code made it easy to get confused. The key things for me were:

    1. I wasn't dealing with a UINavigationController with its out-of-the-box root view controller, I was dealing with a UIViewController. With the UINavigationController, you don't have to do as much. But with a UIViewController, I had to add a UINavigationBar and to that, I had to add a single UINavigationItem. I did this by dragging them from the Object Library to my storyboard.
    2. Don't try to put a UILabel in the UINavigationBar to do what I was trying to do, which was have them call custom functions. Trust me, it doesn't work, at least not in my case.
    3. After figuring out I needed to add a UINavigationItem, the next ah-ha! moment for me was the fact that I could put multiple UIBarButtonItems in it (note: my example in my question shows just one to keep it simple but I'm actually adding three items)
    4. The magic piece of the puzzle was connecting my code to the storyboard. I simply Ctrl-clicked from my UINavigationItem to my view controller and created an @IBOutlet called @IBOutlet weak var navItem: UINavigationItem!
    5. My code that creates the buttons and adds them to the view uses this outlet like so (simplified):

    _

    class CalculatorViewController: UIViewController {
    
      @IBOutlet weak var navItem: UINavigationItem!
    
      override func viewDidLoad() {
        super.viewDidLoad()
    
        let settingsButton = UIBarButtonItem(title: "Settings", style: .Plain, target: self, action: #selector(self.segueToSettings(_:)))
    
        let viewLogButton = UIBarButtonItem(title: "Log", style: .Plain, target: self, action: #selector(self.segueToLog(_:)))
    
        let saveButton = UIBarButtonItem(barButtonSystemItem: .Add, target: self, action: #selector(self.saveLift(_:)))
    
        self.navItem.leftBarButtonItem = settingsButton
        self.navItem.rightBarButtonItems = [saveButton, viewLogButton] 
      }
    
    func saveLift(sender: AnyObject) {
        let weight = weightLifted.text
        let reps = repetitions.text
        let maxAmount = oneRepMax.text
        let unitsText = units.text
    
        coreDataStack.saveLiftEvent(currentLiftName!, formula: currentFormulaName!, weight: weight!, repetitions: reps!, maxAmount: maxAmount!, unitsUsed: unitsText!)
    
        performSegueWithIdentifier("segueToLog", sender: self)
      }
    }
    

    Lastly, when you create that @IBOutlet, don't name it navigationItem: UINavigationItem because that will make Xcode very unhappy:

    enter image description here

    I burned a lot of hours on this one. I hope this information helps somebody avoid that in the future.