MVVM Architecture in SwiftUI Using Generics
Have you ever been frustrated with Xcode’s live previewing of SwiftUI views when working with a MVVM architecture?
As your project grows, and your views and view models become more complex, you might find yourself running into some challenges with being able to use Xcode’s live previewing. As your view model gains more responsibilities it becomes more difficult to preview different view states. Here’s an example.
There’s obviously a few ways to be able to approach this problem, maybe you mock some service layer dependencies that are injected into your view model when initializing from a preview, or some other creative solution. Ideally, you want a solution that is as flexible as possible when working with previews, because you might have some complex state transitions, or just want to test how a view’s animations react to changes in the view model. In this post, we’ll go over how to use Swift generics to create a protocol for your view model, that can be implemented as a mock for your previews for easier state change testing.
Using Generics
I feel as though generics in Swift aren’t seen as commonly as they are in other languages, however, generics are extremely powerful when writing flexible, reusable functions across your codebase. If you haven’t worked with generics before, I recommend checking out the Swift language documentation before continuing.
The General Idea
When working with generics, you’ll want to define a protocol that your view models will conform to, this can be named whatever is relevant for your use-case, but for this example we’ll just name our protocol with the naming convention of our view model + “protocol”.
The View Model Protocol
Since we’re going to be using our protocol as a state object, the protocol itself will need to conform to ObservableObject
. Now, all of our view model implementations can conform to this protocol and allow us to use the protocol as a generic in our view. I’ve also added a sample variable userName
that stores a user’s name, and a sample function updateUserDetails
to send the user’s updated name to the application’s service layer that for example, might update a user’s name on the product’s back-end.
The View
To create our view, we’ll use a generic type parameter ViewModel
on our view example UserInfoView
. We can also define what type our generic type parameter conforms to, in our example it will be UserInfoViewModelProtocol
, the protocol we just created.
Our view has three UI elements, a text field for a user to enter their name, a button so that the user can save the changes to their name, and a text element that displays the status that a user has performed an action, and there are asynchronous tasks happening in the background.
The View Model Mock Implementation
As you’ve seen in the view example, I went ahead and stubbed out in the preview where the mock view model would be implemented. All that’s left to do is create the mock view model.
Our mock view model implements the protocol by updating our variable userDetailsAreUpdating
whenever updateUserDetails()
is called, the view should then react to this state change and show a status message that user details are updating.
The REAL View Model Implementation
Now that our mock is setup, we’ll want to create our “real” implementation of our protocol, so that we can use it in the live version of our app.
Now, whenever we instantiate our view from somewhere else in our app we can use UserInfoView(viewModel: UserInfoViewModel())
to get the “real” view model setup to use. This also gives us some serious flexibility as we can now use other implementations of UserInfoViewModelProtocol
to build more robust features in the future.
Reusability
When using this pattern in some of my indie dev projects, I’ve created code snippets to easily boilerplate this code that you can import yourself, which will are available on Github.
Subscribe to Norfare
Get the latest posts delivered right to your inbox