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?
Saving Game Results
To track the player’s long-term trends, you need a way to save the results of previous games. You’ll only store the turn on which the player successfully guessed the word or lost.
Open GuessingGame.swift and add the following new property to the class:
@AppStorage("GameRecord") var gameRecord = ""
The @AppStorage
property wrapper ties a variable to the UserDefaults, a place you can store key-value pairs consistently across launches of your app. You tie the gameRecord
variable to a key named GameRecord and set it as an empty string if the key does not currently exist in UserDefaults. If the variable changes, SwiftUI will update the value in UserDefaults, and vice-versa.
Now, find the checkGuess()
method in the GuessingGame
class. Look for where you mark a game as won, which reads status = .won
, and add the following code directly before the return
:
gameRecord += "\(currentGuess + 1)"
This will add the number of the turn when the player won the game. Remember that currentGuess
is a zero-based index but the first possible turn is number one. To handle this, you add one to currentGuess
. Move to the next if-else
statement and add the following code after status = .lost
:
gameRecord += "L"
When the player loses a game, this will mark the result with an L. Over time, your app will now track the player’s game history. In the next section, you’ll use that history to show the player how they’re doing.
Displaying Statistics
The work of translating the string stored in the gameRecord
in the previous section will be handled by the GameStatistics
struct found in GameStatistics.swift. Open this and you’ll see it expects a string with the history you stored in the last section.
Open StatisticsView.swift in the ResultsViews group. It expects a GameStatistics
struct when you show the view. You’ll use the data calculated by this struct to build a view showing the player’s game results. Replace the body
of this view with:
VStack(spacing: 15.0) {
VStack {
Text("Game Statistics")
.font(.title)
Text("Played: \(stats.gamesPlayed) ") +
Text("Won: \(stats.gamesWon) (\(stats.percentageWon) %)")
Text("Win Streak: \(stats.currentWinStreak) ") +
Text("Max Streak: \(stats.maxWinStreak)")
}
// Next VStack here
}
This will create a pair of nested VStack
views, with the outer one specifying spacing between the inner ones. This section shows the number of games played, the number of games won and the winning percentage. It also shows the player their current and longest winning streaks. This data all comes from the GameStatistics property passed into the view.
Building a Bar Chart
Now replace the // Next VStack here
comment with the following:
// 1
VStack(alignment: .leading) {
Text("Winning Guess Distribution")
.font(.title)
// 2
let maxDistribution = Double(stats.winRound.max() ?? 1)
// 3
ForEach(stats.winRound.indices, id: \.self) { index in
// 4
let barCount = stats.winRound[index]
let barLength = barCount > 0 ?
Double(barCount) / maxDistribution * 250.0 : 1
HStack {
// 5
Text("\(index + 1):")
Rectangle()
.frame(
width: barLength,
height: 20.0
)
Text("\(barCount)")
}
}
}
This code will produce a bar chart showing the rounds where the player has won games:
- You set the
alignment
of theVStack
toleading
to align all views against the leading edge of the parent view. - The
winRound
property contains an array indicating how many times the player won in the specified number of guesses. Again, remember that winning on the first guess would be at index zero. You get the greatest number in the array to scale your bar chart. If there are no entries, then you use the value 1. - Now you loop through all
indices
of thewinRound
property. The current index will be passed to the closure asindex
. - You get the number of games won with guesses corresponding to the current index. You check if this value is greater than zero. If so, you calculate the ratio between this number and the largest number in the array. You multiply that ratio by 250.0 points to get the length of the bar. If the value for this index was zero, then you use 1 to show a thin line instead of nothing.
- Inside an
HStack
you show three views. First, you show the number of guesses this bar represents (adding one to convert from a zero-based array). You next create a rectangle with the width calculated inbarLength
in step four and a height of 20 points. Last, you display the actual count.
To see the result, change the preview to provide a history:
StatisticsView(stats: GameStatistics(gameRecord: "4L652234L643"))
Show the preview to see the results.
Now you can add this information to the game result view. Open GameResultView.swift in GameBoardViews. Since we’re adding more information, Command + Click on the ShowResultView
and select Embed in VStack. If you don’t see this option, make sure you’re displaying the preview canvas alongside the editor. Change the newly created VStack
to be a ScrollView
. This handles cases where the additional information won’t fit on the screen of smaller devices. Finally, add the following code at the bottom of the ScrollView
:
StatisticsView(
stats: GameStatistics(gameRecord: game.gameRecord)
)
Build and run the app, then play a few games to create a history so you can appreciate the new statistics view.
Adding a Show Stats Button
You’ll now add a button to let the player view these stats anytime. Open ActionBarView.swift. Add the following code before the Spacer()
in the view:
Button {
showStats = true
} label: {
Image(systemName: "chart.bar")
.imageScale(.large)
.accessibilityLabel("Show Stats")
}
This adds a button that, when tapped, will set showStats
to true
.
Open ContentView.swift and add the following after the existing sheet
:
.sheet(isPresented: $showStats) {
StatisticsView(stats: GameStatistics(gameRecord: game.gameRecord))
}
This displays the statistics in a modal view when showStats
is true
.
Build and run the app, then tap the new button to see the stats page.
Congratulations! You’ve now replicated the most visible features of the original Wordle. In the next section, you’ll add a few finishing touches around managing the game state.