
After participating in the State of the Union session at WWDC 2019, I decided to study SwiftUI in detail. I spent a lot of time working with it and now I started to develop a real application that can be useful to a wide range of users.
I called it MovieSwiftUI - this is an app for searching for new and old films, as well as their collection into the collection using the
TMDB API . I have always loved films and even created a company working in this field, really a long time ago. It was difficult to call the company awesome, but the application - yes!
We remind: for all readers of "Habr" - a discount of 10,000 rubles when writing to any Skillbox course on the promotional code "Habr".
')
Skillbox recommends: Online Profession Java Developer Educational Course.
So, what can MovieSwiftUI?
- Interacts with the API - it makes almost any modern application.
- It loads asynchronous data on requests and parses JSON in the Swift model using Codable .
- Shows images uploaded on demand and caches them.
- This application for iOS, iPadOS, and macOS provides the best UX for users of these operating systems.
- The user can generate data, create their own movie lists. The application saves and restores user data.
- Views, components and models are clearly separated using the Redux pattern. The data flow here is unidirectional. It can be fully cached, restored and overwritten.
- The application uses the basic components of SwiftUI, TabbedView, SegmentedControl, NavigationView, Form, Modal, etc. It also provides custom views, gestures, UI / UX.
In fact, the animation is smooth, gif turned out a bit twitchingWorking on the app has given me a lot of experience, and overall it is a positive experience. I was able to write a full-featured application, in September I will improve it and post it in the AppStore, simultaneously with the release of iOS 13.
Redux, BindableObject, and EnvironmentObject

At the moment, I have been working with Redux for about two years now, so I understand it relatively well. In particular, I use it in the frontend for
React web site, as well as for developing native iOS (Swift) and Android (Kotlin) applications.
I have never regretted choosing Redux as the data flow architecture for creating an application on SwiftUI. The most difficult issues when using Redux in a UIKit application are working with the store, as well as receiving and retrieving data and comparing it with your views / components. For this, we had to create a kind of connector library (for ReSwift and ReKotlin). It works well, but quite a lot of code. Unfortunately, it is (yet) not open source.
Good news! The only thing to worry about with SwiftUI - if you plan to use Redux, is the store, states and reducers. The interaction with the store completely takes over SwiftUI thanks to @EnvironmentObject. So, the store starts with BindableObject.
I created a simple Swift package,
SwiftUIFlux , which provides basic Redux usage. In my case, this is part of MovieSwiftUI. I also
wrote a step-by-step tutorial that will help you use this component.
How it works?final public class Store<State: FluxState>: BindableObject { public let willChange = PassthroughSubject<Void, Never>() private(set) public var state: State private func _dispatch(action: Action) { willChange.send() state = reducer(state, action) } }
Every time you launch an action, you activate the gearbox. It will evaluate actions according to the current state of the application. Further, it will return a new modified state in accordance with the type of action and data.
Well, since the store is a BindableObject, it will notify SwiftUI of a change in its value using the willChange property provided by the PassthroughSubject. This is because BindableObject must provide PublisherType, but the protocol implementation is responsible for managing it. All in all, this is a very powerful tool from Apple. Accordingly, in the next rendering cycle, SwiftUI will help to display the body of the views according to the change of state.
Actually, this is all - the heart and magic of SwiftUI. Now, in any submission that is subscribed to a state, the submission will be displayed in accordance with what data is obtained from the state and what has changed.
class SceneDelegate: UIResponder, UIWindowSceneDelegate { var window: UIWindow? func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { if let windowScene = scene as? UIWindowScene { let window = UIWindow(windowScene: windowScene) let controller = UIHostingController(rootView: HomeView().environmentObject(store)) window.rootViewController = controller self.window = window window.makeKeyAndVisible() } } } struct CustomListCoverRow : View { @EnvironmentObject var store: Store<AppState> let movieId: Int var movie: Movie! { return store.state.moviesState.movies[movieId] } var body: some View { HStack(alignment: .center, spacing: 0) { Image(movie.poster) }.listRowInsets(EdgeInsets()) } }
A store is implemented as an EnvironmentObject when the application is started, and then available in any view using @EnvironmentObject. Performance is not impaired because derived properties are quickly retrieved or calculated from the state of the application.
The above code changes the image if the movie poster changes.
And this is actually done in just one line, which is used to connect the views to the state. If you worked with ReSwift on iOS or even
connect with React, you will understand what the magic of SwiftUI is.
And now you can try to activate the action and publish the new state. Here is a more complex example.
struct CustomListDetail : View { @EnvironmentObject var store: Store<AppState> let listId: Int var list: CustomList { store.state.moviesState.customLists[listId]! } var movies: [Int] { list.movies.sortedMoviesIds(by: .byReleaseDate, state: store.state) } var body: some View { List { ForEach(movies) { movie in NavigationLink(destination: MovieDetail(movieId: movie).environmentObject(self.store)) { MovieRow(movieId: movie, displayListImage: false) } }.onDelete { (index) in self.store.dispatch(action: MoviesActions.RemoveMovieFromCustomList(list: self.listId, movie: self.movies[index.first!])) } } } }
In the code above, I use the .onDelete action from SwiftUI for each IP. This allows the line in the list to display the usual iOS swipe to delete. Therefore, when the user touches the delete button, he launches the corresponding action and removes the movie from the list.
Well, since the list property is derived from the BindableObject state and is implemented as EnvironmentObject, SwiftUI updates the list, because ForEach is associated with the calculated movie property.
Here is a part of the MoviesState reductor:
func moviesStateReducer(state: MoviesState, action: Action) -> MoviesState { var state = state switch action {
The reducer is executed when you send an action and return a new state, as mentioned above.
I will not go into details yet - how does SwiftUI really know what to display. In order to understand this more deeply, you should
look at the WWDC session on data flow in SwiftUI. It also tells in detail why and when to use
State , @Binding, ObjectBinding and EnvironmentObject.
Skillbox recommends: