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.

4.7 (19) · 2 Reviews

Download materials
Save for later
Share

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.
Note: This intermediate-level tutorial assumes you’re comfortable building an iOS app using Xcode and writing Swift. You should already have used Combine and be comfortable with its concepts. The example also uses SwiftUI.

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.

Three Ducks main three screens

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.

Two cards for Three Ducks, one with three ducks and one with a horse

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.

It’s the vibe: Rather than being a set of inflexible rules, Redux has a lot of flexibility built-in. Instead of thinking of it like a brutalist concrete structure that dominates your code, try thinking of it more like a hand-built wooden dining-room table — an artful piece of technology you can eat at, but how you cook your roast is up to you.

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.

Three Ducks title screen preview

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.

Three Ducks game screen preview

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.