NEW: Master Swift design patterns with my latest book! >>

< Previous: Loading a level: addTarget and shuffling arrays   Next: Property observers: didSet >

It's play time: index(of:) and joined()

We need to add three more methods to our view controller in order to finish this game: one to handle letter buttons being tapped, another to handle the current word being cleared, and a third to handle the current word being submitted. The first two are easiest, so let's get those done so we can get onto the serious stuff.

First, we already used the addTarget() method in viewDidLoad() to make all our letter buttons call the method letterTapped(), but right now it’s empty. Please fill it in like this:

@objc func letterTapped(btn: UIButton) {
    currentAnswer.text = currentAnswer.text! + btn.titleLabel!.text!
    activatedButtons.append(btn)
    btn.isHidden = true
}

That does three things: gets the text from the title label of the button that was tapped and appends it to the current text of the answer text field, then appends the button to the activatedButtons array, and finally hides the button. We need to force unwrap both the title label and its text, because both might not exist – and yet we know they do.

The activatedButtons array is being used to hold all buttons that the player has tapped before submitting their answer. This is important because we're hiding each button as it is tapped, so when the user taps "Clear" we need to know which buttons are currently in use so we can re-show them. You already created an empty @IBAction method for clear being tapped, so fill it in like this:

@IBAction func clearTapped(_ sender: Any) {
    currentAnswer.text = ""

    for btn in activatedButtons {
        btn.isHidden = false
    }

    activatedButtons.removeAll()
}

As you can see, this method removes the text from the current answer text field, unhides all the activated buttons, then removes all the items from the activatedButtons array.

That just leaves one final method, and you already created its stub: the submitTapped() method for when the player taps the submit button.

This method will use another new function called index(of:), which searches through an array for an item and, if it finds it, tells you its position. The return value is optional so that in situations where nothing is found you won't get a value back, so we need to unwrap its return value carefully.

If the user gets an answer correct, we're going to change the answers label so that rather than saying "7 LETTERS" it says "HAUNTED", so they know which ones they have solved already. The way we're going to do this is delightfully simple: index(of:) will tell us which solution matched their word, then we can use that position to find the matching clue text. All we need to do is split the answer label text up by \n, replace the line at the solution position with the solution itself, then re-join the answers label back together.

You've already learned how to use components(separatedBy:) to split text into an array, and now it's time to meet its counterpart: joined(separator:). This makes an array into a single string, with each array element separated by the string specified in its parameter.

Once that's done, we clear the current answer text field and add one to the score. If the score is evenly divisible by 7, we know they have found all seven words so we're going to show a UIAlertController that will prompt the user to go to the next level.

The "evenly divisible" task is easy to do in Swift (and indeed any sensible programming language) thanks to a dedicated modulo operator: %. Modulo is division with remainder, so 10 % 3 means "tell me what number remains when you divide 10 evenly into 3 parts". 3 goes into 10 three times (making nine), with remainder 1, so 10 % 3 is 1, 11 % 3 is 2, and 12 % 3 is 0 – i.e., 12 divides perfectly into 3 with no remainder. If score % 7 is 0, we know they have answered all seven words correctly.

That's all the parts explained, so here's the final submitTapped() method:

@IBAction func submitTapped(_ sender: Any) {
    if let solutionPosition = solutions.index(of: currentAnswer.text!) {
        activatedButtons.removeAll()

        var splitAnswers = answersLabel.text!.components(separatedBy: "\n")
        splitAnswers[solutionPosition] = currentAnswer.text!
        answersLabel.text = splitAnswers.joined(separator: "\n")

        currentAnswer.text = ""
        score += 1

        if score % 7 == 0 {
            let ac = UIAlertController(title: "Well done!", message: "Are you ready for the next level?", preferredStyle: .alert)
            ac.addAction(UIAlertAction(title: "Let's go!", style: .default, handler: levelUp))
            present(ac, animated: true)
        }
    }
}

The levelUp() call in there is just to get you started – there isn't a level up here, because I only created one level! But if you wanted to make more levels and continue the game, you'd need a levelUp() method something like this:

func levelUp(action: UIAlertAction) {
    level += 1
    solutions.removeAll(keepingCapacity: true)

    loadLevel()

    for btn in letterButtons {
        btn.isHidden = false
    }
}

As you can see, that code clears out the existing solutions array before refilling it inside loadLevel(). Then of course you'd need to create level2.txt, level3.txt and so on. To get you started, I've made an example level2.txt for you inside the Content folder – try adding that to the project and see what you think. Any further levels are for you to do – just make sure there's a total of 20 letter groups each time!

Get the ultimate experience

The Swift Power Pack includes my first six books for one low price, helping you jumpstart a new career in iOS development – check it out!

< Previous: Loading a level: addTarget and shuffling arrays   Next: Property observers: didSet >
Click here to visit the Hacking with Swift store >>