Now that our game is all set up, the last part of this project is to make sure the user can’t enter invalid words. We’re going to implement this as four small methods, each of which perform exactly one check: is the word original (it hasn’t been used already), is the word possible (they aren’t trying to spell “car” from “silkworm”), and is the word real (it’s an actual English word).
If you were paying attention you’ll have noticed that was only three methods – that’s because the fourth method will be there to make showing error messages easier.
Anyway, let’s start with the first method: this will accept a string as its only parameter, and return true or false depending on whether the word has been used before or not. We already have a usedWords
array, so we can pass the word into its contains()
method and send the result back like this:
func isOriginal(word: String) -> Bool {
!usedWords.contains(word)
}
That’s one method down!
The next one is slightly trickier: how can we check whether a random word can be made out of the letters from another random word?
There are a couple of ways we could tackle this, but the easiest one is this: if we create a variable copy of the root word, we can then loop over each letter of the user’s input word to see if that letter exists in our copy. If it does, we remove it from the copy (so it can’t be used twice), then continue. If we make it to the end of the user’s word successfully then the word is good, otherwise there’s a mistake and we return false.
So, here’s our second method:
func isPossible(word: String) -> Bool {
var tempWord = rootWord
for letter in word {
if let pos = tempWord.firstIndex(of: letter) {
tempWord.remove(at: pos)
} else {
return false
}
}
return true
}
The final method is harder, because we need to use UITextChecker
from UIKit. In order to bridge Swift strings to Objective-C strings safely, we need to create an instance of NSRange
using the UTF-16 count of our Swift string. This isn’t nice, I know, but I’m afraid it’s unavoidable until Apple cleans up these APIs.
So, our last method will make an instance of UITextChecker
, which is responsible for scanning strings for misspelled words. We’ll then create an NSRange
to scan the entire length of our string, then call rangeOfMisspelledWord()
on our text checker so that it looks for wrong words. When that finishes we’ll get back another NSRange
telling us where the misspelled word was found, but if the word was OK the location for that range will be the special value NSNotFound
.
So, here’s our final method:
func isReal(word: String) -> Bool {
let checker = UITextChecker()
let range = NSRange(location: 0, length: word.utf16.count)
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
return misspelledRange.location == NSNotFound
}
Before we can use those three, I want to add some code to make showing error alerts easier. First, we need some properties to control our alerts:
@State private var errorTitle = ""
@State private var errorMessage = ""
@State private var showingError = false
Now we can add a method that sets the title and message based on the parameters it receives, then flips the showingError
Boolean to true:
func wordError(title: String, message: String) {
errorTitle = title
errorMessage = message
showingError = true
}
We can then pass those directly on to SwiftUI by adding an alert()
modifier below .onAppear()
:
.alert(errorTitle, isPresented: $showingError) {
Button("OK") { }
} message: {
Text(errorMessage)
}
We’ve done that several times now, so hopefully it’s becoming second nature! In fact, now that you're familiar with basic alerts like that one, here's a pro tip for you: if you don't include any buttons in the code, you automatically get a simple "OK" button that dismisses the alert.
So, if you prefer you can write this:
.alert(errorTitle, isPresented: $showingError) { } message: {
Text(errorMessage)
}
Yes, that looks a bit odd – choose whichever style you prefer.
At long last it’s time to finish our game: replace the // extra validation to come
comment in addNewWord()
with this:
guard isOriginal(word: answer) else {
wordError(title: "Word used already", message: "Be more original")
return
}
guard isPossible(word: answer) else {
wordError(title: "Word not possible", message: "You can't spell that word from '\(rootWord)'!")
return
}
guard isReal(word: answer) else {
wordError(title: "Word not recognized", message: "You can't just make them up, you know!")
return
}
If you run the app now you should find that it will refuse to let you use words if they fail our tests – trying a duplicate word won’t work, words that can’t be spelled from the root word won’t work, and gibberish words won’t work either.
That’s another app done – good job!
SAVE 50% All our books and bundles are half price for Black Friday, so you can take your Swift knowledge further without spending big! Get the Swift Power Pack to build your iOS career faster, get the Swift Platform Pack to builds apps for macOS, watchOS, and beyond, or get the Swift Plus Pack to learn advanced design patterns, testing skills, and more.
Link copied to your pasteboard.