Getting a Redux Vibe Into SwiftUI
Learn how to implement Redux concepts to manage the state of your SwiftUI app in a more predictable way by implementing a matching-pairs card game. By Andrew Tetlaw.
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
Getting a Redux Vibe Into SwiftUI
30 mins
- Getting Started
- Exploring Three Ducks
- SwiftUI, Meet Redux
- Creating the App State
- Creating the Store
- Observing State Changes
- Creating Actions
- Making a Reducer
- Dispatching Actions
- Putting Your Reducer to Work
- Vibing out to Redux
- Flipping the Cards
- Adding Your First Middleware
- Unflipping Cards
- Adding Middleware to the Store
- Winning the Game
- Where to Go From Here?
- Game Difficulty
- High Score
- Quack!
Is there a mobile software engineer who doesn’t quake in fear at the mention of app state management? For mobile apps, managing state is a significantly hairy problem. Redux is a JavaScript library for predictably managing state, most commonly used in React. Applying Redux concepts to SwiftUI is a viable solution for state management in your next iOS app — and definitely worth a moment of your time to explore.
In this tutorial, you’ll complete a fun card-matching game by implementing the state management in Redux concepts. You’ll learn:
- About immutable states.
- How to take advantage of them in a mobile app.
- To think in terms of actions.
- About using actions to change the app state.
- How to classify certain behaviors as side effects of actions.
- To apply side effects in a predictable way.
Getting Started
Download the starter project using the Download Materials button at the top or bottom of this tutorial.
Three Ducks is your new game: a challenging game of animal card matching. At the moment, it doesn’t do anything because you haven’t written any state code.
No use ducking for cover, it’s time to dive into the amazing world of Redux! You’ll soon see that SwiftUI and Redux work so well together that they’re like siblings hatched from the same nest.
Now, open the starter project and see what you have so far.
Exploring Three Ducks
Build and run the app, and you’ll see the title screen. You can also see the SwiftUI previews if you look through all the views.
Three Ducks is a card-matching game with three screens:
- The title screen shows a play button.
- The game screen displays a grid of cards, a moves counter and an exit button.
- Finally, the win screen gives you a congratulatory message.
The cards have a front view and a back view. The back view is identical for all cards, but the front view shows an image of the animal represented by the card.
You’re missing a lot of code. You’ll need a way to start the game, a way to flip the cards and some game logic. It’s time to stop splish-splashing about, roll up your sleeves and get to work!
SwiftUI, Meet Redux
The official definition of Redux is “a predictable state container for JavaScript apps”. However, instead of asking, “What is Redux?”, you should ask, “What does Three Ducks need to know to start?”. In the lexicon of Redux, that would be the state. State is an object — traditionally, an immutable object.
What happens when you tap New Game? That’s an example of an action. Every time the user performs an action, the state of the app needs to change. How? That’s the job of the reducer. The reducer understands the action, updates the state, if needed, and returns a new state.
All of this is wrapped up in the store: the place where everything happens.
The philosophy of SwiftUI matches Redux nicely. Views have state and only update when the state changes, much like Redux. In fact, the internals of SwiftUI feel like they’re a kind of reducer.
If you want to catch the vibe and explore the theory, go to the source: redux.js.org. Redux Essentials is a great place to start.
If you want to catch the vibe and explore the theory, go to the source: redux.js.org. Redux Essentials is a great place to start.
Creating the App State
So, what does the app need to know to start? The first step is to make an object to represent the state of the app. The best approach is to always start simple, so make it a Swift struct
.
Add a new top-level group in Xcode called State; this is where all your state management code will live.
Next, add a new file named State.swift to this group. Finally, add a new struct
called ThreeDucksState
in State.swift:
struct ThreeDucksState {
}
You’ll use ThreeDucksState
to track your app state. The first thing the app needs to know is when to start the game. Add a property to ThreeDucksState
named gameState
:
var gameState: GameState = .title
GameState
is already defined under the Model group. There are three possible cases: title
, started
and won
. Each case represents a screen to display. The default value is title
. When the game starts, it should be set to started
. won
is for when the game is won.
Creating the Store
Of course, the app needs to know where the state is, so that value can be read. Add a new file under the State group named Store.swift. Add the following code in that file:
class Store<State>: ObservableObject {
@Published private(set) var state: State
init(initial: State) {
self.state = initial
}
}
You’ve added a touch of Swift generics here, which will be useful in the future. Store
is initialized with an initial state of a generic type State
and stores that in a published property. The object implements ObservableObject
so your views can observe it for changes.
Still in Store.swift, add this above the Store
type definition:
typealias ThreeDucksStore = Store<ThreeDucksState>
You need a concrete type for your Three Ducks app, so typealias
will be handy. With this type alias, you have a Store that’s tied to ThreeDucksState
.
Observing State Changes
Open AppMain.swift and add the following modifier to ContentView()
:
.environmentObject(ThreeDucksStore(initial: ThreeDucksState()))
At app launch, you initialize the store with a default state and pass it to your ContentView
as an environment variable.
Next, open ContentView.swift and add a new property to ContentView
for the store as an environment object:
@EnvironmentObject var store: ThreeDucksStore
Now, remove TitleScreenView()
from the body
of ContentView
and replace it with the following switch
statement:
switch store.state.gameState {
case .title:
TitleScreenView()
case .started:
GameScreenView()
case .won:
GameWinScreenView()
}
Now the app will show a specific view for each game state. You can check your handiwork as long as your preview has its own environment object. Still in ContentView.swift, find ContentView_Previews
and add the following to the end of body
:
.environmentObject(ThreeDucksStore(initial: ThreeDucksState()))
Click Resume on the preview canvas window, and you’ll see the title screen.
You can change the default value of gameState
in ThreeDucksState
to .started
. If you do so and come back to ContentView.swift and refresh the preview, you’ll see the game screen.
You can try this with .won
too. Now that you’ve confirmed observing the store object is working, set the default gameState
back to .title
.
SwiftUI already has easy-to-implement object observing features, which is one of the big reasons Redux and SwiftUI get along so well. Using @EnvironmentObject
makes it even easier, because when you inject a view with an environment object using .environmentObject(_:)
, it’ll automatically share that object with any child views.