Review
Time for a new challenge but first, let’s review what we have learned in this huge lesson:
- The placeholder text for a
UILabel
is shown in gray when the text field is empty. - Using
UIFont.systemFont(ofSize:)
ensures our app uses the default system font for iOS (which will keep us safe for future versions as well. - To run a method when a button is tapped we need to call
addTarget()
on the button. - Activating multiple constraints at once is faster than modifying
isActive
for each one. - A property observer is code that gets run when their property changes or is about to change.
- We can remove whitespace from a string using the
trimmingCharacters(in:)
method. - The
shuffle()
method shuffles an array in place rather than returning a new array. - We can split strings into an array the
components(separatedBy:)
method. - We can use methods for the handler of a
UIAlertAction
. - We can use
+=
to append one array to another. - The safe area layout guide is the space available to our view, excluding rounded corners and notches.
UILabel
is responsible for showing static text.
Challenges
Challenge 1: draw a thin grey line around the buttons view to make it stand out from the rest of the UI.
This seems a good challenge for remembering what was taught in project 2 which, of course, I didn’t remember by heart and had to go back and check. My solution for this is around line 82:
buttonsView.layer.borderWidth = 1
buttonsView.layer.borderColor = UIColor.darkGray.cgColor
I had previously read, in the rush, “around the buttons’s view”, thinking that every button needed to have a border. That looked quite awful, though, and I reverted it immediately.
Here is the actual result:

Challenge 2: if the user enters an incorrect guess, show an alert telling them they are wrong.
For this challenge we will need to extend the submitTapped()
method so that if firstIndex(of:)
failed to find the guess you show the alert.
I created an else
statement to the if let
block of the submitTapped()
method like this:
else {
let errorAlertController = UIAlertController(title: "That's not correct!", message: "Please try again...", preferredStyle: .alert)
errorAlertController.addAction(UIAlertAction(title: "OK", style: .default, handler: restart))
present(errorAlertController, animated: true)
}
At first there was no handler
in my addAction
segment but, trying it, I noticed that clicking on the OK button would have required the user to also click the clear button and this, to me, would be extra work to be asked our users who paid so much money for our … oh wait… never mind…
I therefore though to connect the clearTapped
method but the addAction
initialiser expects an optional handler of type ((UIAlertAction) -> Void)? = nil
while our clearTapped
method is of type (UIButton) -> Void)
.
As far as my knowledge goes it is not possible to convert one to the other so I created another method called restart
that, yes, does exactly the same thing as clearTapped
.
This worked, of course, but looked very inelegant to me so I decided to write my first closure ever inside the addAction
method. I have no idea if it is correct but a) Xcode is not complaining b) it is working so, why fix what’s not broken?! Here is my code:
else {
let errorAlertController = UIAlertController(title: "That's not correct!", message: "Please try again...", preferredStyle: .alert)
errorAlertController.addAction(UIAlertAction(title: "OK", style: .default) {
[weak currentAnswer] _ in
currentAnswer?.text = ""
for button in self.activatedButtons {
button.isHidden = false
}
self.activatedButtons.removeAll()
})
present(errorAlertController, animated: true)
}
I guess the weak currentAnswer
is needed so that we do not get extra problems afterwards in the game. The _
should be correct because we are not fiddling with the UIAlertAction
in our closure.
I then pasted the code from the other method, set currentAnswer
to be optional and added self.
where Xcode was asking me to. I thought about adding [weak self]
as well but the amount of errors and of ?
and !
needed to fix them discouraged me from doing so.
I would be very curious to see where your solution led.
Challenge 3: try making the game also deduct points if the player makes an incorrect guess.
The issue here is not really about deducing points but how to move to the next level because at the moment we are using a %7
operator to move on. This would prevent even someone who just makes a single mistake to move on. We should either check for all the buttons being hidden or we can set up another property to count how many items were matched so that we can allow for proceeding when a certain amount of correct answers is given.
Insert about an hour or so here…
This challenge gave me a bit more of an headache but, again, it was because I was not clear enough in my intents. The machine was always doing what I was telling her to do, just… I was telling her the wrong thing.
My approach here has been the following: I created a variable to store the amount of hidden buttons. I also added a property observer because I used it during the debugging phase (I would have never gotten through this without that didSet
thing!):
var hiddenButtons = 0 {
didSet {
print("Hidden buttons: \(hiddenButtons)")
}
}
I then added a call to hiddenButtons += 1
in the letterTapped
method so that every time a letter is tapped and is therefore hidden, our variable gets a +1
increment. Accordingly, I put a -= 1
in every place where the contrary was happening. I performed a big refactor at the end so it would be useless now to show you where I put that change.
I then wrapped the score checker into an if
statement saying that, if the hidden buttons property would have been equal to 20 we could have proceeded to the analysis of the results. Then, inside, I modified the original if
statement adding two else if
ones. One if the score was >= 5
and one if it was < 5
(quite unforgiving, right?). In the case one would have scored 5 or 6 points I added two alert actions, one to go to the next level and one to stay and repeat this level! Here is the code.
if hiddenButtons == 20 {
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)
} else if score >= 5 {
let passAlertController = UIAlertController(title: "Not bad!", message: "You can proceed or repeat the level!", preferredStyle: .alert)
passAlertController.addAction(UIAlertAction(title: "Next level!", style: .default, handler: levelUp))
passAlertController.addAction(UIAlertAction(title: "Let's stay here!", style: .cancel, handler: restartLevel))
present(passAlertController, animated: true)
} else if score < 5 {
let failureAlertController = UIAlertController(title: "Not good enough!", message: "Please try again", preferredStyle: .alert)
failureAlertController.addAction(UIAlertAction(title: "Restart", style: .default, handler: restartLevel))
present(failureAlertController, animated: true)
}
}
Now you may notice a new method: restartLevel
. Here it is, with the companion refactoring method:
func restartLevel(action: UIAlertAction) {
solutions.removeAll(keepingCapacity: true)
loadLevel()
showLetterButtons()
}
func showLetterButtons() {
for button in letterButtons {
button.isHidden = false
hiddenButtons -= 1
}
}
Oh, I forgot: I put the score -= 1
inside the outermost else
statement of the submitTapped
method, a score = 0
to reset the score after every level in the loadLevel
method (which now gets called extensively) and a call to hiddenButtons -= 1
inside clearTapped
and the aforementioned else
closure.
I have the feeling that the other way might have been easier but my head is kind of burning now and I cannot work on it now.
Of course we could improve vastly on this game, such as making an alert that shows that there is, by now, nothing beyond the second level or making the interface more pleasing to the eyes but, for the purpose of this project, this seems already quite a good thing!
Generally speaking I loved the logic behind the game and how it made us think more and more!
As always, I appreciate your comments and feedbacks.
Let me know how you solved these challenges.
You can find the repository for the project here.
If you like what I’m doing here please consider liking this article and sharing it with some of your peers. If you are feeling like being really awesome, please consider making a small donation to support my studies and my writing (please appreciate that I am not using advertisement on my articles).
If you are interested in my music engraving and my publications don’t forget visit my Facebook page and the pages where I publish my scores (Gumroad, SheetMusicPlus, ScoreExchange and on Apple Books).
You can also support me by buying Paul Hudson’s books from this Affiliate Link.
Anyways, thank you so much for reading!
Till the next one!