Code Structure in SwiftUI - Part 1: Separating model and view
SwiftUI is incredibly easy to get started with. However, dig a bit deeper and you’ll also find it highly capable of more advanced operations. Much akin to the philosophy of the Swift language itself - approachable for beginners yet comprehensive for experts.
By its nature, SwiftUI encourages a departure from typical app architecture on Apple platforms. MVC is less relevant here in this new, declarative world. MVVM (albeit not exclusively) feels like a much better fit.
Over the next few posts, I’ll be focusing on how you can structure your code base when working with SwiftUI. We’ll tackle a small app which will help illustrate the following:
- How to make best use of various SwiftUI goodies as they were intended (property wrappers such as observable objects, state and bindings)
- How to separate out generic, reusable UI components from your concrete views specific to your app’s domain
- How to keep types small, light and easy to adapt whenever your app evolves.
The overall aim is simply to provide some repeatable patterns that might improve your SwiftUI workflows.
Let's say we’re building an app that fetches stats about NBA basketball players to display them in a variety of ways.
This is just enough model code to get us started. Let’s take the quickest path to get our UI up and running first and foremost. We’ll do so by injecting our Player model directly into our SwiftUI View.
With only a small extension to our Stat model we’re able to format the data and display it in a List.
Now we have the absolute basics in place, let’s begin to refactor to accommodate typical situations we’d experience in a real world application.
Firstly, our app would be fetching the data from somewhere; most likely over the network. Currently, our view has a direct dependency on our model data. Let’s introduce an intermediate type - a view model - to handle the model-view relationship.
Here, we have removed the direct dependency on model data from our view. Our new view model object, instead, takes a type which can load player data (it doesn’t care where from) and exposes the Player type as a read only property.
However, we encounter a problem here. How do we notify the view when data has been loaded? Before SwiftUI, we might have implemented one of several different approaches for communicating from model to view: NotificationCenter, a delegate-style protocol or even KVO. These don’t necessarily work too well with SwiftUI’s declarative syntax.
Thankfully, SwiftUI provides an elegant solution that allows views to be updated whenever a change happens elsewhere in our app.
Quick aside. It’s actually the Combine framework which provides both ObservableObject and @Published. They’re type-aliased into Foundation, but it is SwiftUI which relies on them to perform its updating magic.
There are two important additions here; conformance to the ObservableObject protocol, and use of the @Published property wrapper. The first indicates that our view model would like SwiftUI to observe its changes. And the second indicates which properties we would like to publish when changed.
Now, whenever our player property gets set or mutated, SwiftUI will update our view accordingly (often with an animation thrown in for free; as is the case with the List type.)
Finally, let’s add the missing piece to our view so that we load the data when the view appears.
The onAppear view modifier does exactly what you’d expect. And we use that to call our view model’s load method.
We’ve seen how MVVM can work well with SwiftUI and it’s way of handling data flow. This has allowed us to get the very basics of our app working with a drastic reduction in ‘glue’ code we typically would have had to write in a UIKit app.
In the next post, we will continue to build on our example app to better handle the loading state; whilst also decomposing our view into reusable UI components.