SwiftUI Opinion: ViewModel doesn't belong in Previews

I love technology and everything related, from gadgets to new professional techniques. I like thinking, researching, optimizing, inventing and developing. I have a strong background in software research and development, operating systems, Voice-over-IP, network security, wired and wireless network engineering, complemented with electronic engineering background.
My career goal is to always keep learning, to be challenged, and to work remotely so I can be present for my family.
Bug hacker and master troubleshooter, my strength is understanding a problem and getting to the root of it. I'm mostly a self-taught individual and a constant learner. I push my technical boundaries daily and search for ways to improve my skills every day. With over 20 years of experience writing software in various languages, creating or optimizing algorithms, the digital development world is my turf.
Sample challenges which I particularly enjoyed:
- Created a GLSL based magnification tool for a client who was turned down by three other companies as "impossible to do on macOS".
- Optimized several SQL queries to reduce load time of a particular web page from several seconds to sub 50ms.
- Identified the root cause of stuttering animations in iOS mobile app and implemented mitigation strategy
Specialties: Swift, Objective-C and PHP Software Development; TCP/IP and Wireless Network Engineering
SwiftUI Previews are key to making SwiftUI development fast and easy. However, if your views require a ViewModel, you are impeding your own development speed and re-usability of the view.
Let’s imagine that you create a view and it’s view model. Everything works, the project is released and everyone is happy. A couple months after, a new feature is added and you need another view that looks 100% like the one you implemented, except the logic in how the view is used is entirely different.
You find yourself having to either duplicate the SwiftUI view, or refactor the one you had created so it’s no longer tightly coupled, jeopardizing the stability of the already developed feature.
If your view requires interaction with a view model, you should extract all the visible parts of the view into its own SwiftUI view, keeping it agnostic of the view model. You end up with two SwiftUI views, one with knowledge of the view model that doesn’t have any design whatsoever in it, and another with all the design and no reference to the view model.
Example of what NOT to do:
import SwiftUI
struct BadDesignView: View {
@StateObject var viewModel: LoginViewModel
var body: some View {
VStack {
if viewModel.showLoginForm {
TextField("Username", text: $viewModel.enteredUserName)
Button("Login", action: viewModel.login)
}
if let username = viewModel.loggedInUser {
Text("Greetings, \(username)")
}
}
}
}
struct BadDesignView_Previews: PreviewProvider {
static let loggedInVm: LoginViewModel = {
let vm = LoginViewModel()
vm.enteredUserName = "Mike Mousey"
vm.login()
return vm
}()
static let loggedOutVm: LoginViewModel = LoginViewModel()
static var previews: some View {
BadDesignView(viewModel: loggedOutVm)
.previewDisplayName("Logged Out")
.previewLayout(.sizeThatFits)
BadDesignView(viewModel: loggedInVm)
.previewDisplayName("Logged In")
.previewLayout(.sizeThatFits)
}
}
Example of reusable design:
import SwiftUI
struct ReusableDesignView: View {
let showLoginForm: Bool
@Binding var enteredUserName: String
let loggedInUser: String?
let loginAction: () -> Void
var body: some View {
VStack {
if showLoginForm {
TextField("Username", text: $enteredUserName)
Button("Login", action: loginAction)
}
if let username = loggedInUser {
Text("Greetings, \(username)")
}
}
}
}
struct ReusableDesignView_Previews: PreviewProvider {
static var previews: some View {
ReusableDesignView(
showLoginForm: true,
enteredUserName: .constant(""),
loggedInUser: nil,
loginAction: { /* do nothing */ })
ReusableDesignView(
showLoginForm: false,
enteredUserName: .constant(""),
loggedInUser: "Mike Mousey",
loginAction: { /* do nothing */ })
}
}
import SwiftUI
struct LoginView: View {
@StateObject var viewModel = LoginViewModel()
var body: some View {
ReusableDesignView(
showLoginForm: viewModel.showLoginForm,
enteredUserName: $viewModel.enteredUserName,
loggedInUser: viewModel.loggedInUser,
loginAction: viewModel.login)
}
}
In a MVVM architecture, the logic is moved into a view model to allow testing the logic separately from the user interface.
Isolating a designed view from the view model improves re-usability of the view.
As a side effect, creating your Previews will be easier. You no longer have to find a way to bring the view model into the expected state, or have to create mock view models.





