Making a Real World Application With SwiftUI
Part one: data flow and Redux
Since the State of the Union session at the WWDC 2019, I’ve been engrossed in SwiftUI. I’ve used almost all my spare time to play with it. Now, I’ve decided to make a real world application.
But what defines a real world application?
Well, let me dig into the details of an open source application I’m making exclusively with SwiftUI. This is MovieSwiftUI — an application for browsing, collecting and discovering movies using the TMDB API ️. I’ve always loved movies, and I made a company around that a long time ago. It didn’t work out that great, but the app (a social network around movies) was awesome!
Back on track, what does MovieSwiftUI technically do?
- It talks to an API, a thing most «client» apps do those days.
- It loads async data on demand and parses the JSON into Swift models using Codable.
- It displays images downloaded on demand, and caches them.
- It’s a single target iOS, iPadOS, and macOS application with conditional UI implementation to provide the best UX on iOS, iPadOS, and macOS. It might move to a specific macOS target later in the future.
- The user can generate data and create his own movies lists. The app will save and restore the user data.
- Views, components and models are cleanly separated as the app uses a custom implementation of the Redux pattern. It’s a single source of truth and a unidirectional data flow for the whole application state. It can be fully cached, restored, and rewinded.
- It uses basic SwiftUI components, TabbedView, SegmentedControl, NavigationView, Form, Modal etc… It also provides very custom views, gestures, and UI/UX. Yes, it’s also a SwiftUI playground/demo as much as it is a full featured application.
So far it’s been a very successful experience. I’ve been able to write a full featured application that I’ll evolve and release on the App Store in September with the iOS 13 release.
Redux, ObservableObject and EnvironmentObject
With SwiftUI, it felt totally right to choose Redux as a data flow architecture. Some of the most complex parts of Redux when using it in a UIKit app are how you subscribe to your store and how you derive and extract data from your state and map them to your views/components props. We had to build a sort of connector library for doing that (on top on ReSwift and ReKotlin). It’s working well, but it’s quite a bit of code. And, sadly, not (yet) open source.
Well good news! With SwiftUI, if you want to use the Redux pattern, the only things you have to worry about are your store, states, and reducers. The subscription part is entirely handled by SwiftUI thanks to
@EnvironmentObject property wrapper and our store being a
I’ve made a simple Swift package, SwiftUIFlux, which provides a very basic implementation of Redux. I use this as part of MovieSwiftUI. I’ve also added a comprehensive step by step tutorial in the readme on how to use it.
How does it work?
Anytime you dispatch an action, you’ll trigger your reducer. It’ll evaluate your action against your current app state, and return a new modified state according to your action type and data.
Store is an
ObservableObject, and the
state property is wrapped in a
@Published property wrapper, SwiftUI will be notified whenever the
state property is set. This is a very powerful helper from the Combine framework. SwiftUI will call the body of your views and diff it according to what changed in your state. And the part of the view with different data derived from your state will be re-rendered accordingly.
This is the heart and magic of SwiftUI + Redux. Now, in any view subscribed thought
@EnvironmentObject to the store.state property, the view will be re-rendered according to what data it derives from the state and what changed.
The store is injected as an
EnvironmentObjectwhen the app is launched, and then available in any view using the
@EnvironmentObject property wrapper. There is no performance hit if your derived properties are fast to extract or compute from your app state.
In the code above, if the movie poster changes, the image will be updated accordingly.
Your views are in fact subscribed to your state. You wrote only one line of code to connect it, and one more line to extract/map a property to a view property from it. If you worked with ReSwift on iOS, or even @connect on React, you’ll see very quickly why it is so magical in SwiftUI.
So then, how do you trigger an action and publish a new state? Let’s look at a more complex example.
In the code above, I use the
.onDelete action from SwiftUI for each API. It allows the row in your list to display the standard iOS swipe to delete. So when the user touches the delete button, it triggers an action on my state, and removes the movie from the list.
As the list property is derived from the state which is a
ObservableObjectand injected as an
EnvironmentObject, SwiftUI will update the list because
ForEach is bound to the
movies computed property.
Here is a part of the reducer for the
The reducer is executed when you dispatch an action and return a new state, as said above.
I’ll not dig into how SwiftUI actually knows what to render, but the view diffing is done at the view level. It compares the previous version and a new one. So it’ll stay very efficient, no matter the size of your state! What matters is how you extract the properties from your state and the complexity of your views, not the size of your state.
I hope this piece enlightens you on how to architect your data flow with SwiftUI. Of course there are many other valid architectures, and Apple provides a lot of tools with SwiftUI and Combine. Nothing is forcing you to have your whole app state into one big struct, like Redux does. It’s just one of the approaches, and it make a lot of sense in SwiftUI, in my opinion.
I invite you to watch this precious WWDC session about data flow in SwiftUI. This is a very good explanation in order to understand why and when to use
Let me know if you have any questions or feedback. I’ll be glade to answer them and update the piece accordingly.