iOS Swift – Building a Times Tables App – Part 2

Last post, we put together the UI layout for a times times tables app in Swift using Xcode. The UI elements all work and are in their correct positions, but there’s no logic to make them do anything. Today we’ll add a dash of logic to the home screen so that we can gather up all the information we need to show the user the times tables flash cards they want to see. You can find parts 1 and 3 of this series here:

iOS Times Tables App – Part 1
iOS Times Tables App – Part 3

To really understand what I’m talking about in this post, you’ll either want to look at the last post (link above) or take at the code for the ViewController in my github account:

https://github.com/jamesmcclay/swift_times_tables

Adding variables for selected values

There are three different values that the user could potentially provide to the app, and we need to account for all of them. The first is the table – for example, if the user wanted to practice 3×2, 3×3, 3×4 and so on, they would provide 3 in the “table” field. The second is the max multiplier, which is the second number. In the Android version, we set the default to be 12, we’ll keep with that. But the user can change the default by entering something in the max multiplier field. The last is the mode – normal or random. We’ll add some global variables at the top of the class, just below the UI elements variables:

var currentMode:String {
    get {
        if let mode = UserDefaults.standard.value(forKey: "mode") as? String {
            return mode
        } else {
            return "normal"
        }
    }
    set {
        UserDefaults.standard.setValue(newValue, forKey: "mode")
    }
}
var currentTable:Int {
    get {
        if let table = UserDefaults.standard.value(forKey: "table") as? Int {
            return table
        }else{
            return 0
        }
    }
    set {
        UserDefaults.standard.setValue(newValue, forKey: "table")
    }
}
var currentMultiplier:Int {
    get {
        if let multiplier = UserDefaults.standard.value(forKey: "multiplier") as? Int {
            return multiplier
        }else{
            return 0
        }
    }
    set {
        UserDefaults.standard.setValue(newValue, forKey: "multiplier")
    }
}

If you’re not familiar with “computed properties” in Swift, this probably looks pretty foreign. Basically, a computed property is a variable that doesn’t hold a value. Normally, variables hold values. Instead, you write code to get and set the value. The get and set code runs when elsewhere in your code you write something that either gets or sets the variable’s value. That’s why there’s a get and set part (technically set is optional).

The reason we’re doing this here is because we want the values to be stored in the UserDefaults file. UserDefaults is just a text file where you can store some key-value data that you can access even after the app or phone restarts. Because of that persistence, the app can “remember” values that were entered last time so the user can avoid re-entering their favorite table, max multiplier or mode. We’ll also be able access the values from the next view in the app with the actual flash cards without having to “send” it like in Android. For getting, it also lets us account for the possibility that there will be no value in UserDefaults, and return a default in that case. The get blocks return “normal” by default for the mode, and 0 by default for the table and multiplier.

Populate the table and max multiplier

We’ll use the code we just wrote to put values in the table and max multiplier text fields. In the setupInputs function we’ll add the following snippet:

if currentTable != 0 {
    tableInput.text = String(currentTable)
}
if currentMultiplier != 0 {
    multiplierInput.text = String(currentMultiplier)
}

Remember, since currentTable and currentMultiplier are computed, these lines simply run the get blocks that we wrote earlier for these variables. If the values stored in UserDefaults were not 0, we’re setting the the textfields to be whatever was stored. The reason why we avoid pre-populating 0 is because we’ll use 0 in the startTapped function to mean that the user accepted the defaults and entered nothing. In that case, we don’t need to pre-populate anything. We’ll add this snippet to startTapped:

if let tableText = tableInput.text {
    if let table = Int(tableText) {
        currentTable = table
    }else{
        currentTable = 0
    }
}else{
    currentTable = 0
}
if let multiText = multiplierInput.text {
    if let multiplier = Int(multiText) {
        currentMultiplier = multiplier
    }else{
        currentMultiplier = 0
    }
}else {
    currentMultiplier = 0
}

This code, which runs when the user taps the start button, will grab whatever the user entered in the text fields and runs the set blocks that we wrote earlier. We check first to see if there’s any text at all, then to see if it will properly change to an Integer. If both of those pass, we set the entered value to UserDefaults. If either fails, we set the UserDefault value to 0.

Setting the mode

In Android, we were able to create a radio group with two multually exclusive options, normal and random. In Swift and UIKit, no such thing exists. So what we’ll use is UISwitch, which is a little switcher button thingy. We already laid them out in the last post, but we need to make sure they work right. When one is selected, the other can’t also be selected. We’ll achieve that with the following code in modeTapped:

modes.forEach { $0.isOn = false }
sender.isOn = true
switch sender.tag {
case 0:
    currentMode = "normal"
case 1:
    currentMode = "random"
default:
    currentMode = "normal"
}

That first line simply unchecks both of the UISwitch elements in the modes array. Remember creating that array? It just holds the switches so we can loop through them. Then we turn the switch on for whichever switch fired the function, followed by setting currentMode to either “normal” or “random” based on whether the sender’s tag is 0 or 1. We set the tags when we created the switches, you might have forgotten 🙂

We’ll “remember” whatever setting the user selected last time they opened the app by adding this snippet to setupModes:

if currentMode == "random" {
    randomMode.isOn = true
}else{
    normalMode.isOn = true
}

This once again pulls the mode value from UserDefaults.

Dismiss keyboard

I ran into an issue when I realized I couldn’t dismiss the keyboard once it appeared for the text fields. I got it to go away by adding UITextFieldDelegate to my ViewController’s class declaration so it looks like this:

class ViewController: UIViewController, UITextFieldDelegate {

And for each text field, made the ViewController the delegate in the setupInputs function:

tableInput.delegate = self
multiplierInput.delegate = self

Finally, I added this function which is available if your ViewController has been made the UITextFieldDelegate:

func textFieldShouldReturn(_ textField: UITextField) -> Bool {
    textField.resignFirstResponder()
    return true
}

It basically just tells the keyboard to go away when return is tapped, if they keyboard appeared for a text field.

Try it out!

Our app in the Xcode iOS simulator

Not a whole lot about the look has changed since the last post, but one thing you can do is print the values of currentTable, currentMultiplier and currentMode in the console. You can also enter some, press start, and then close/restart the app. You should be able to see the values persist.

Stay tuned for the next post, where we’ll create the screen for the flash cards!

Leave a Reply

Your email address will not be published.