Reputation: 5374
I have this view which takes as a param its own View Model. Inside this view I need to show another view which in order for it to be displayed, needs its own View Model. So I present the Purchase
view and then inside the Purchase
view I need to display the details of the actual item which was purchased. So I pass as a parameter also the ItemViewModel
. Both PurchaseViewModel
and ItemViewModel
are used on other views also.
It could happen in the feature that I would also need ClientViewModel
which would be used to display data about the actual buyer of this item. Does this mean that I would need to pass also @ObservedObject var clientViewModel: ClientViewModel
as a parameter?
My question is: Is this approach good or there is a better way to do this ?
struct PurchaseView: View {
@ObservedObject var purchaseViewModel: PurchaseViewModel
@ObservedObject var itemViewModel: ItemViewModel
var body: some View {
VStack {
Text(purchaseViewModel.title)
ItemView(itemViewModel: itemViewModel)
}
}
}
class Purchase {
let senderID: String
let receiverID: String
var status: PurchaseStatus
var item: Item?
}
Upvotes: 0
Views: 1879
Reputation: 115051
You should start by considering the relationship of the models, not the view models.
For example, you could make a case that a Purchase
involves one or more items (Item
) and a purchaser (Client
). In this case you would actually create a model something like:
class Purchase {
var items = [Item]()
var client: Client
...
}
And your PurchaseViewModel
would reflect this, containing the ItemViewModel
and ClientViewModel
let clientViewModel: ClientViewModel
let itemsViewModels: [ItemViewModel]
init(purchase: Purchase) {
self.clientViewModel = ClientViewModel(client:purchase.client)
self.itemsViewModels = purchase.items.map { ItemViewModel(item:$0) }
}
If you have a hierarchy of views that need some unrelated models then you can consider using the environment, especially for models that are "global"
For example, say that the user was looking at an item detail, but they were not yet making a purchase (So there is no Purchase
yet) and you want to have a button that shows them their Client
detail.
You could pass the ItemViewModel
explicitly to the ItemDetailView
:
struct ItemListView {
...
Button(action: { ItemDetailView(item:someItemView) })
{ ... }
}
struct ItemDetailView {
...
Button(action: { ClientView() }) {
Text("Client detail")
}
}
struct ClientDetailView {
@EnvironmentObject client: ClientViewModel
...
}
You can inject the ClientViewModel
into the environment at a suitable point, such as the root view or even scene delegate.
If you have a hierarchy of views that need these models, but not all views need all models, you could use the environment to provide the various models rather than directly injecting them via initialiser parameters.
Upvotes: 1
Reputation: 13296
You could make the PurchaseViewModel
contain the ItemViewModel
and ClientViewModel
as properties in the same way that the PurchaseView
contains the ItemView
and ClientView
.
That would make PurchaseViewModel
responsible for creating the instances of the other view models. The parent of PurchaseView
would no longer need to know about the implementation of PurchaseView
.
Upvotes: 2