How to Make a Game Like Wordle in SwiftUI: Part Two
Extend your Wordle word-game clone with animation, accessibility, statistics and shareable results, all in SwiftUI. By Bill Morefield.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
How to Make a Game Like Wordle in SwiftUI: Part Two
35 mins
- Getting Started
- Showing the Player’s Result
- Colorizing the Keyboard
- Adding Animation
- Creating a Shake Animation
- Improving Accessibility
- Sharing Game Results
- Adding a Share Button
- Saving Game Results
- Displaying Statistics
- Building a Bar Chart
- Adding a Show Stats Button
- Remembering Where the Player Is
- Where to Go From Here?
Remembering Where the Player Is
There’s a rather annoying bug in the game right now. Run the app and make a couple of guesses.
Now force quit. When you restart the game, you’ll lose your progress. Whenever the app gets stopped or cleared from memory, the player loses their current game. To fix that, you’ll need a way to store the game’s current state and know when to load and save that state. Open GuessingGame.swift and add the new property:
@AppStorage("GameState") var gameState = ""
Again you’ll use the @AppStorage
property wrapper to store information in UserDefaults. Next, add the following method to the end of the class:
func saveState() {
let guessList =
guesses.map { $0.status == .complete ? "\($0.letters)>" : $0.letters }
let guessedKeys = guessList.joined()
gameState = "\(targetWord)|\(guessedKeys)"
print("Saving current game state: \(gameState)")
}
This uses the map
method on the guesses
array. Each guess will become a string with the letters in the guess plus a > for complete guesses. You join the elements of the string array into a single string. You then store a string consisting of the targetWord
, a | separator, and then the combined string that represents the current guesses. Next, you’ll add a method to bring this game state back in.
Add the following method to the end of the class:
func loadState() {
// 1
print("Loading game state: \(gameState)")
currentGuess = 0
guesses = .init()
guesses.append(Guess())
status = .inprogress
// 2
let stateParts = gameState.split(separator: "|")
// 3
targetWord = String(stateParts[0])
// 4
guard stateParts.count > 1 else { return }
let guessList = String(stateParts[1])
// 5
let letters = Array(guessList)
for letter in letters {
let newGuess = String(letter)
addKey(letter: newGuess)
}
}
Loading the state is a bit more complicated but just reverses the steps:
- First you reset the game by setting the current guess to zero, clearing the
guesses
array before adding an empty guess and then setting thestatus
toinprogress
. - Next, you take the
gameState
string and split it at the | character to get an array of two strings. - You set the target word to the first string of this array.
- You ensure there is a second string, which might not be the case if the player began a game, but didn’t enter any letters. If there are letters, you store them in
guessList
. - You now convert the
guessList
into an array of characters and then loop through each letter. You convert each letter to a String and then calladdKey(letter:)
with that letter. The result rebuilds the game state in the same way as if the player tapped the letters directly.
One more thing in this class; any time the game completes, you want to clear any saved state.
At the top of the class, find the declaration of the status
property and replace it with:
var status: GameState = .initializing {
didSet {
if status == .lost || status == .won {
gameState = ""
}
}
}
Whenever the status
property changes, you use the didSet
property observer to check if the new status is lost
or won
. If so, you set the gameState
to an empty string since the game completed and no longer needs to be restored.
Now, you need to update the app to store and load the game when needed. Open ContentView.swift and add a new property at the top:
@Environment(\.scenePhase) var scenePhase
The scenePhase
environment variable indicates a scene’s operational state. By monitoring changes, you can tell when you need to load or save the game state.
Add the following code after the existing use of onChange(of:perform:)
:
// 1
.onChange(of: scenePhase) { newPhase in
// 2
if newPhase == .active {
if game.status == .new && !game.gameState.isEmpty {
game.loadState()
}
}
// 3
if newPhase == .background || newPhase == .inactive {
game.saveState()
}
}
This code manages the state by:
- Monitoring changes in the
scenePhase
property. The new state will be passed into the closure asnewPhase
. - If the app becomes active, you check if the
status
of the game isnew
, meaning no player action has taken place and thegameState
property isn’t empty. The presence of stored state implies there’s a game to restore and is why you clear the state when a game ends. If both are true, then tell the game to load the saved state. In the case where the app hadn’t been pushed from memory, thestatus
of the game will beinprogress
so it retains the player’s current efforts. - Any time the app goes into the
background
state, you tell the game to store its current state in case it’s needed later.
Build and run the app, start a game and then force quit. You should see the console message telling you the app saved the state. Start the app again and the game will pick up where you left off.
Where to Go From Here?
You can use the Download Materials button at the top and bottom of this tutorial to download the starter and final projects. You’ve built a nice Wordle clone in SwiftUI and, in the process, learned about modeling the game, building an app and adding finishing touches to improve the player’s experience.
Don’t forget to read part one of this tutorial on building this app.
For more about accessibility, see the iOS Accessibility in SwiftUI Tutorial series.
For more on animation in SwiftUI, see Chapter 19 of SwiftUI by Tutorials.
We hope you enjoyed this tutorial. If you have any questions or comments, please join the forum discussion below!