Let me preface this by saying that this is not a problem. This is a solution I stumbled upon that works really well. I need clarification as to why this works and whether this is a feature because I haven't found a lot of info that describes this. I have a large app that employs this throughout so I need to make sure this solution will stick around in the long term.
I needed a way to pass in many parameters to a new unique View
, and to use those parameters to create a network request that was called only once. An example would be a ProductsView
which consists of a List
of products. When an individual product is tapped within the List
a NavigationLink
segues to a ProductView
. The parameters are passed to create a network request within the ProductViewModel
which is then used to update the ProductView
.
Some things I tried include...
Creating a @StateObject var productViewModel = ProductViewModel
within ProductsView
. That didn't work because each ProductView
is unique and needs it's own ProductViewModel
.
Using @Binding
vars within ProductView
and then passing them into ProductViewModel
within .onAppear
to make a network request. That didn't work because the @Binding
vars didn't work with nested views within ProductView
.
Using regular vars such as var productID: Int
or @State var productID: Int
. This worked but proved to be way too messy and the network request being called within .onAppear
was unreliable being called multiple times in some cases.
https://stackoverflow.com/a/62636048/6724254 way to hacky for me.
The solution I found that works the best is to create ProductViewModels
ProductView(productViewModel: ProductViewModel(productID: product.productID)))
within the NavigationLink
destination call. A very simple example...
import SwiftUI
struct Product: Identifiable{
var id = UUID()
var productID = 0
var name = ""
}
class ProductViewModel: ObservableObject {
@Published var product = Product()
init(productID: Int){
print("init ProductViewModel ProductID: \(productID)")
getFromNetwork(productID: productID)
}
func getFromNetwork(productID: Int){
// network request made here
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.product = Product(productID: productID, name: "Product Name \(productID)")
}
}
}
// Each ProductView must be unique
struct ProductView: View {
@StateObject var productViewModel: ProductViewModel
var body: some View {
if productViewModel.product.productID > 0{
VStack{
Text("Current Product: " + productViewModel.product.name)
NavigationLink(destination: ProductView(productViewModel: ProductViewModel(productID: Int.random(in: 1..<100)))) {
Text("Recommended Product")
}
}
}
}
}
struct ProductsView: View {
@State var products = [Product(productID: 1), Product(productID: 2), Product(productID: 3), Product(productID: 4), Product(productID: 5), Product(productID: 6)]
var body: some View {
NavigationView {
List(products) { product in
NavigationLink(destination: ProductView(productViewModel: ProductViewModel(productID: product.productID))) {
Text("Product ID: \(product.productID)")
}
}
.navigationTitle("Products")
}
}
}
So my question is whether this solution is reliable in ways that I haven't foreseen, and will it stick around in the long term?