It’s day 30 of the 100 Days of SwiftUI! Yesterday, we had our introduction to our latest project called Word Scramble, as well learned about the List
view. Today, we’re implementing our game. We’ll see lots of interesting new pieces of code come by, so strap in. It’s time to dive in!
Word Scramble in SwiftUI: implementation
We’re allowing users to enter words. These words need to be saved somewhere and they also need to be displayed to our users. This is where the List
comes in. Before we create the list, we’ll set up all the properties we’ll need for our game. I’ve added comments to explain what they’re used for.
@State private var usedWords = [String]() // array that contains the words a user has entered
@State private var rootWord = "" // The word we're spelling from
@State private var newWord = "" // Used to clear the text field by putting in an empty String
@State private var errorTitle = "" // Used as title for our alert in case of an error
@State private var errorMessage = "" // Used as message for our alert in case of an error
@State private var showingError = false // Used to control whether our error alert is shown
Next, we’ll create our List
. It consists of a NavigationView
and two sections. I’ve added comments just about everywhere that explain what the code does.
var body: some View {
NavigationView {
List {
Section {
TextField("Enter your own word", text: $newWord)
.autocapitalization(.none) // removes auto capitialization of words for aesthetic purposes
}
Section {
ForEach(usedWords, id: \.self) { word in
HStack {
Image(systemName: "\(word.count).circle") // adds a number displaying how many letters our word contains in a circle (symbol)
Text(word) // loops through our usedWords array and displays it in the section.
}
}
}
}
.navigationTitle(rootWord) // Title shown at the top of our app
.onSubmit(addNewWord) // onSubmit means, when the user presses the enter key. In this case, when the press enter, run the addNewWord function so that the word that's entered get's added to the array.
.onAppear(perform: startGame) // When the app starts, run function startGame
.alert(errorTitle, isPresented: $showingError) {
Button("OK", role: .cancel) {}
} message: {
Text(errorMessage)
}
}
}
Functions for Word Scramble
You’ve already seen a few function calls in the example above. Time to peak behind the curtain and see what they do!
func addNewWord() {
let answer = newWord.lowercased().trimmingCharacters(in: .whitespacesAndNewlines) // Lowercases answer, trims whitespaces and line breaks
guard answer.count > 0 else { return } // Checks if the String isn't empty
guard isOriginal(word: answer) else { // calls the isOriginal and wordError function
wordError(title: "Word used already.", message: "Be more original!")
return
}
guard isPossible(word: answer) else { // calls the isPossible and wordError function
wordError(title: "Word not possible.", message: "You can't spell that word from \(rootWord)")
return
}
guard isReal(word: answer) else { // calls the isReal and wordError function
wordError(title: "Word not recognized.", message: "You can't just make up words, you know!")
return
}
withAnimation { // adds an animation
usedWords.insert((answer), at: 0) // inserts at the start of the array so that it is shown on top of the list in the app
}
newWord = ""
}
func startGame() {
if let startWorldsUrl = Bundle.main.url(forResource: "start", withExtension: "txt") { // check if our txt file can be found in the app bundle
if let startWords = try? String(contentsOf: startWorldsUrl) { // if it's found, do something ->
let allWords = startWords.components(separatedBy: "\n") // create an array of all items that are on a new line
rootWord = allWords.randomElement() ?? "silkworm" // should never run, but just in case
return
}
}
fatalError("Could not load start.txt from bundle.") // Should not happen, but if it does, it will show this error and crash the app
}
func isOriginal(word: String) -> Bool {
!usedWords.contains(word) // checks whether the array already contains a word and if it doesn, returns false
}
func isPossible(word: String) -> Bool {
var tempWord = rootWord // make a copy of our rootword in a new variable
for letter in word { // loops over the tempWord and removes a letter if it's found, so that it cannot be used again
if let pos = tempWord.firstIndex(of: letter) {
tempWord.remove(at: pos)
} else {
return false
}
}
return true
}
func isReal(word: String) -> Bool { // checks whether text (in this case a word) is valid English
let checker = UITextChecker() // create an instance of a UITextChecker
let range = NSRange(location: 0, length: word.utf16.count) // The range of our text to be checked. Location 0 means start at the start. The length is the full length of the word variable we'll pass in when our function is called
let misspelledRange = checker.rangeOfMisspelledWord(in: word, range: range, startingAt: 0, wrap: false, language: "en")
return misspelledRange.location == NSNotFound
}
func wordError(title: String, message: String) { // function that allows us to easily set up errors depending on the context
errorTitle = title
errorMessage = message
showingError = true
}
With our functions implemented, our Word Scramble app is now completed.
Screenshots
Here’s a few screenshots of our app as we’ve implemented with the code examples above. We could do a lot more with the design, but this project was focused on teaching us about List
as well as the advanced String
functions.



And that’s it for day 30! Tomorrow, we’ll review our project as well complete a few challenges. Time to recharge and get ready!
100 Days of SwiftUI – Day 30