Each button has a function, but those functions are all empty right now. The CalculationDelegate is capable of performing the logic, but it still needs to get called by these functions.
numberButtonPressed
The CalculationDelegate has the function handleInput(_ number: Int)
. The function accepts an Integer (1, 2, 3, etc.) as an argument. The question is, how to we know what Int
to pass in to the function? The naïve approach would be to use a separate function for each button on screen, like this for the 9 button:
@IBAction func nineButtonPressed(_ sender: UIButton) {
calculation.handleInput(9)
}
Each UIButton
on screen is wired to connect to this same function, so it wouldn’t make any sense for them to all pass the same constant to handleInput
. How can we find out what button was actually pressed?
(sender: UIButton)
The key part here is that every IBAction also has a sender
argument. When a button is pressed, the linked function is called with the pressed button as the sender. This gives us access to a UIButton
representation of our button on screen. That means we can get attributes like the button’s title label (the visible text).
@IBAction func numberButtonPressed(_ sender: UIButton) {
let buttonTitle = sender.titleLabel!.text!
let buttonNumber = Int(buttonTitle)! //converts String to Int
calculations.handleInput(buttonNumber)
//also update the text on screen
resultLabel.text = calculations.resultNumber.roundedString
}
Operators
Next up is the operators. Even though we have four @IBAction functions, we’re going to be using a fifth method so we don’t have to do basically the same thing in each of the four operator functions. It’s always better form to break something into a separate function if you’re doing it more than once.
func setOperator(_ character: String, withFunction function: @escaping (Double, Double) -> Double) {
...
}
(Double, Double) -> Double
Just like any other argument, (Double, Double) -> Double
is a type. This time, though, it represents a function. In Swift, functions are types just like everything else. It reads as a function that takes two doubles and returns a double. More on this in a sec, because it’s gonna get real cool.
Here’s the actual method body (just throw it somewhere at the bottom of the ViewController.swift):
func setOperator(_ character: String, withFunction function: @escaping (Double, Double) -> (Double)) {
//DefaultOperator is part of the default CalculationDelegate
let customOperator = DefaultOperator(forCharacter: character, withFunction: function)
calculations.setOperator(customOperator)
//again, update the text on screen
resultLabel.text = calculations.resultNumber.roundedString
}
@escaping
The one weird bit here is the @escaping
annotation. This boils down to an esoteric distinction between espacing and nonescaping closures (a function can be called a closure when it’s used as a method parameter). Closures are nonescaping by default. This means they can’t escape the function they exist in, either by passing them to a different function or by assigning them to a variable. Nonescaping functions have some benefits and can be compiler-optimized, but they won’t work for us in this situation.
Marking the closure @escaping
allows it to escape the context of the function. We escape the closure when we call DefaultOperator(forCharacter: character, withFunction: function)
, because we’re passing the function
to the DefaultOperator
. Since we’re violating the default nonescaping behavior, the code wouldn’t compile without the @escaping
annotation.
Calling the function
Now we can go back to looking at the @IBActions themselves. Because of how we implemented the setOperator
function, this is gonna be really slick:
@IBAction func addButtonPressed(_ sender: UIButton) {
setOperator("+", withFunction: +)
}
Whaaaaat??????
That’s right. We just used the language’s addition operator as an argument in a function. And it makes perfect sense if you think about it. ⌘-Clicking on something in Xcode takes you to where that something is defined in code, so ⌘-Click on the plus sign to see its declaration in the Swift standard library:
@warn_unused_result
public func +(lhs: Double, rhs: Double) -> Double
The operator is really just a function that takes two doubles and returns a double. Which is exactly the argument type that the setOperator
function is looking for. Is that crazy or what?? (It’s not crazy, Swift is just insanely self-consistant.)
We can do the same thing for the rest of the operators, giving us this beautiful little group:
@IBAction func addButtonPressed(_ sender: UIButton) {
setOperator("+", withFunction: +)
}
@IBAction func subtractButtonPressed(_ sender: UIButton) {
setOperator("-", withFunction: -)
}
@IBAction func multiplyButtonPressed(_ sender: UIButton) {
setOperator("*", withFunction: *)
}
@IBAction func divideButtonPressed(_ sender: UIButton) {
setOperator("/", withFunction: /)
}
Equals and Clear
These two are pretty trivial, mostly because the logic is bundled up in the CalculationDelegate.
@IBAction func equalsButtonPressed(_ sender: AnyObject) {
calculations.clearInputAndSave(true)
resultLabel.text = calculations.resultNumber.roundedString
}
@IBAction func clearButtonPressed(_ sender: UIButton) {
calculations.clearInputAndSave(false)
resultLabel.text = calculations.resultNumber.roundedString
}
For both of them, we simply pass work on to the CalculationDelegate and then update the text on screen. Simple enough.
Recap
We connected the logic contained in the CalculationDelegate to the interface by writing code for our IBOutlets.
Next Time
We will test the calculator and make sure everything is in working order.