this SwiftUI app uses a NavigationView with one dedicated view model for each of the two involved views.
What bothers me is the creation of the second view model (DetailsViewModel
) for each of the shown links regardless of whether that link is ever going to be pressed or not. You can see this in console log.
This is a minimal example, imagine the data for the second view model coming from different entities, even different data stores and the func makeDetailsViewModel(id:)
to be expensive in its execution.
I would like to defer the creation of the DetailsViewModel
until the user has clicked one of the links so that only that one needs to be created instead of the potential dozens or or hundreds more others. It seams reasonable to me and straight forward in UIKit by injecting the view model in prepare(for segue:)
but I don’t know how to achieve something like this using SwiftUI.
Apple’s tutorial Building Lists and Navigation and other tutorials I found use the same view model or none at all (e. g. Text("Second view")
) for the second view.
How can this be done or is there a completely different way in SwiftUI to go about this? I may be mentally stuck in UIKit.
Thank you.
import SwiftUI
var people: [Person] = [
Person(id: 1, name: "Alice", age: 13, country: "USA"),
Person(id: 2, name: "Brad", age: 29, country: "Canada"),
Person(id: 3, name: "Chad", age: 29, country: "Canada"),
Person(id: 4, name: "Dorothy", age: 62, country: "Ireland"),
Person(id: 5, name: "Elizabeth", age: 40, country: "Sweden"),
Person(id: 6, name: "Francois", age: 21, country: "France"),
Person(id: 7, name: "Gary", age: 36, country: "Singapore"),
Person(id: 8, name: "Hans", age: 28, country: "Germany"),
Person(id: 9, name: "Ivan", age: 70, country: "Russia"),
Person(id: 10, name: "Jaime", age: 45, country: "Spain"),
]
var nameList: [ListEntryViewModel] = {
people.map { person in
return ListEntryViewModel(id: person.id, name: person.name)
}
}()
func makeDetailsViewModel(id: Int) -> DetailsViewModel {
print("Creating view model for DetailView")
let person = people.first(where: { $0.id == id})!
return DetailsViewModel(name: person.name, age: person.age, country: person.country)
}
// MARK: - Model
struct Person {
var id: Int
var name: String
var age: Int
var country: String
}
// MARK: - View Models
struct ListEntryViewModel: Identifiable {
var id: Int
var name: String
}
struct DetailsViewModel {
var name: String
var age: Int
var country: String
}
// MARK: - Views
struct DetailView: View {
var detailsViewModel: DetailsViewModel
var body: some View {
VStack {
Text(detailsViewModel.name)
Text("\(detailsViewModel.age) years")
Text("from \(detailsViewModel.country)")
}
}
}
struct ContentView: View {
var people: [ListEntryViewModel] = nameList
var body: some View {
NavigationView {
List(people) { person in
NavigationLink(destination: DetailView(detailsViewModel: makeDetailsViewModel(id: person.id))) {
Text(person.name)
}
}
.navigationTitle("People")
}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}