How do I connect a control to a method programmatically? - Develop In Swift Fundamentals - Programmatic Actions

I am reading the free book from Apple Education titled "Develop In Swift Fundamentals". I am on the chapter titled "Controls In Action" and the section of the chapter I'm stuck on is titled "Programmatic Actions" (page 287).
The section has me connect a UIButton programmatically by first manually typing in

Code Block
@IBOutlet var button: UIButton!


then connecting the button using this code below.

Code Block
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)

I pasted the code above right after the viewDidLoad function, however I get a fatal error.

Code Block
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value

I've attached a link to the Github repository if you want to download the code and run it in Xcode.
[https://github.com/campusgateapp/CommonInputControls)


Code Block
import UIKit
class ViewController: UIViewController {
    @IBOutlet var toggle: UISwitch!
    @IBOutlet var slider: UISlider!
    @IBOutlet var button: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
    }
    @IBAction func buttonTapped(_ sender: Any) {
        print("Button was tapped!")
        if toggle.isOn {
            print("The switch is on!")
        } else {
            print("The switch is off.")
        }
        print("The slider is set to \(slider.value).")
    }
    @IBAction func switchToggled(_ sender: UISwitch) {
        if sender.isOn {
            print("Switch is on!")
        } else {
            print("Switch is off!")
        }
    }
    @IBAction func sliderValueChanged(_ sender: UISlider) {
        print(sender.value)
    }
    @IBAction func keyboardReturnKeyTapped(_ sender: UITextField) {
        if let text = sender.text {
            print(text)
        }
    }
    @IBAction func textChanged(_ sender: UITextField) {
        if let text = sender.text {
            print(text)
        }
    }
    @IBAction func respondToTapGesture(_ sender: UITapGestureRecognizer) {
        let location = sender.location(in: view)
        print(location)
    }
}

You are probably missing some steps.

You declare the IBOutlet.
That means you have a button in the view in Interface Builder. Right ?
If so, you need to connect the 2 by dragging from the circle next on the left of IBOutlet declaration to the button in Interface Builder.
Did you do this ?
Then the white circle should become black.

then connecting the button using this code below.

Here you do not connect the button, you declare its action.

I pasted the code above right after the viewDidLoad

Where did you put this exactly ? It should probably be INSIDE viewDidLoad.

So please show the complete code of the class, that will be easier to analyse.

Did you solve your issue ?

If not, please explain what the problem is and provide some answer to the questions.
Thank you @Claude31 for your continued help.
To answer your questions. "That means you have a button in the view in Interface Builder. Right ?", "Did you do this?"
Yes, I do have a button in the view in interface builder.
No, I did not drag to connect because the exercise is teaching me how to connect programmatically, not by ⌃ + click + drag.

However, your solution does work. If I ⌃ + click + drag from the empty circle to the button, it does connect. But repeating once again, I'm not trying to use the ⌃ + click + drag feature. I want to connect just by typing code (programmatically, I think it's called).

This is my code.

Code Block
import UIKit
class ViewController: UIViewController {
    @IBOutlet var toggle: UISwitch!
    @IBOutlet var slider: UISlider!
    @IBOutlet var button: UIButton!
    override func viewDidLoad() {
        super.viewDidLoad()
        /*Do any additional setup after loading the view.*/
        button.addTarget(self, action: #selector(buttonTapped(_:)),
           for: .touchUpInside)
    }
    @IBAction func buttonTapped(_ sender: Any) {
        print("Button was tapped!")
        if toggle.isOn {
            print("The switch is on!")
        } else {
            print("The switch is off.")
        }
        print("The slider is set to \(slider.value).")
    }
    @IBAction func switchToggled(_ sender: UISwitch) {
        if sender.isOn {
            print("Switch is on!")
        } else {
            print("Switch is off!")
        }
    }
    @IBAction func sliderValueChanged(_ sender: UISlider) {
        print(sender.value)
    }
    
    @IBAction func keyboardReturnKeyTapped(_ sender: UITextField) {
        if let text = sender.text {
            print(text)
        }
    }
    @IBAction func textChanged(_ sender: UITextField) {
        if let text = sender.text {
            print(text)
        }
    }
    @IBAction func respondToTapGesture(_ sender: UITapGestureRecognizer) {
        let location = sender.location(in: view)
        print(location)
    }
}



I looked at the book.

On page 272, they add the IBAction by control-drag.
They do not declare an IBOutlet here, because they don't need it yet (just need the IBAction)

On page 287, they now need to reference the button, hence they have created the IBOutlet and connected it to the button in IB.

« In order to connect the button to a method programmatically, you’ll need a reference to the button in code. Use Interface Builder to create an IBOutlet for the button.»

You can do it, either
  • by control drag from the button to the code and select IBOutlet (similar to what they explain for IBAction and to what is explained on page 282.

  • or declare IBOutlet in code and control- drag to the button in IB to connect it: That is what they assume Page 287:


In the book, they do not create the button programmatically. In fact, they create it in IB.
If you declare an IBOutlet, you have to connect to the object in IB.
Otherwise when you reference the button, you get the crash
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value.

However, if you really wanted to create the button completely programmatically, you must not declare an IBOutlet, just a var. So change code as follows:

Code Block
class ViewController: UIViewController {
@IBOutlet var toggle: UISwitch! // you may have to do as with button
@IBOutlet var slider: UISlider! // you may have to do as with button
   var button: UIButton! // <<-- No IBOutlet
override func viewDidLoad() {
super.viewDidLoad()
  button = UIButton(frame: CGRect(x: 80, y: 30, width: 100, height: 30)) // <<-- Create the button
button.setTitle("My button", for: UIControl.State.normal) // Select a better title
button.addTarget(self, action: #selector(buttonTapped(_:)), for: .touchUpInside)
view.addSubview(button) // <<-- so that button appears in view - could also write self.view to make it clearer
}

Note: when you connect the button from IB to its IBOutlet, that is somehow the code for initialising button is added automatically when the VC is loaded from storyboard.

Did you solve your problem ?

If so, don't forget to close the thread.
Otherwise, please explain what is the remaining problem.
@beatzbyzach

Did you solve ?
If so, don't forget to close the thread.
Otherwise, please tell what the remaining problem is.

If you have given up on your project, thanks to close the thread anyway.
How do I connect a control to a method programmatically? - Develop In Swift Fundamentals - Programmatic Actions
 
 
Q