3

Using View Models in SwiftUI

For a clean separation of concerns, I want to use my SwiftUI views with view models.

I have two views, a ListView and a DetailView where the first pushes the latter when any list item is tapped – a classic navigation view.

I define a viewModel property on my DetailView as follows and mark it as an observed object in order to update the view when any of its published properties change:

struct DetailView: View {
    @ObservedObject var viewModel: DetailViewModel

    var body: some View { 
        Text(viewModel.text)
    }
}

This approach requires me to pass a viewModel to the DetailView's initializer when I create the view inside the ListView:

struct ListView: View {
    let animals = ["", "", "", "", ""]

    var body: some View {
        List {
            ForEach(animals, id: \.self) { animal in
                NavigationLink(
                    destination: DetailView(viewModel: DetailViewModel(text: animal))
                ) {
                    Text(animal)
                }
            }
        }
    }
}

where DetailViewModel here is simply defined as follows:

class DetailViewModel: ObservableObject {
    @Published var text: String
}

(A view model is certainly over-engineered here, but it's just for the sake of explaining the problem.)

⚡️ The Problem

As the destination views for all navigations links are created each time the list view's body is updated, so are the respective view models. That's not much of a problem for a simple view model as shown above, but it certainly becomes a problem with more complex real-world view models. For example, a view model might instantiate other classes that are rather expensive to initialize. This would be done at the time of showing the ListView for each and every DetailView (model) – regardless of whether it's ever going to be shown (needed) or not.

Thus, it seems to me like an anti-pattern to create view models inside the bodies of other views. Still, it is the most common practice I've seen in code examples and articles across the Internet.

❓ My Question

Is there a way to keep a SwiftUI view from instantiating the destinations views of NavigationLinks immediately? Or is there a way to create view models for view to be used as navigation link destinations outside of a view's body?

Mischa
  • 15,816
  • 8
  • 59
  • 117
  • 2
    Does this answer your question https://stackoverflow.com/a/60295011/12299030? – Asperi Sep 09 '20 at 10:45
  • Yup, the `LazyView` definitely solves the problem of instantiating all destination views and their view models too early. Thanks for that reference! However, it still feels a bit more like a workaround to me. I think, in general, a view model for the next view should not be instantiated inside a _SwiftUI_ view's body, as there is also the ownership issue mentioned in the answer you linked. But I understand that this is more of an architectural question... – Mischa Sep 09 '20 at 12:01
  • I think you can use a StateObject to do what you need to do: https://developer.apple.com/documentation/swiftui/stateobject. Unrelated to the question I'd recommend using https://github.com/pointfreeco/swift-composable-architecture for building SwiftUI apps as it fixes a lot of problems that vanilla SwiftUI has such as this one – Daniel Amarante Sep 09 '20 at 18:23
  • What approach you end up going with? Creating a view model for a detail view should be so difficult.. unless i'm really missing something – DogCoffee Oct 16 '20 at 00:37

0 Answers0