11

I have a view called PurchaseView. This view displays details about the purchase, what was purchased and who purchased it. What I'm doing is that in this view im putting both the ItemView and ClientView inside PurchaseView. ItemView and ClientView are shared and there are used in other parts of my app. They have their own ViewModels.

I have also tried to put ItemViewModel and ClientViewModel inside PurchaseViewModel but I do not know if it is ok to put an ObservableObject inside another ObservableObject. Is this a good approach or there should not be any ObservableObject inside an ObservableObject? Which one of the following is better?

This?

class PurchaseViewModel: ObservableObject {
    let clientViewModel: ClientViewModel
    let itemsViewModel: ItemViewModel

    //
}

Or this?

struct PurchaseView: View {
    @ObservedObject var purchaseViewModel: PurchaseViewModel
    @ObservedObject var itemViewModel: ItemViewModel
    @ObservedObject var clientViewModel: ClientViewModel

    var body: some View {
        //
    }
}

Purchase model:

class Purchase {
    let id: String
    let total: Double
    // ...
    var item: Item?
    var client: Client?
}
pawello2222
  • 46,897
  • 22
  • 145
  • 209
M1X
  • 4,971
  • 10
  • 61
  • 123
  • I could be mistaken (never tried it), but I don't think changes in the inner nested `ObservableObject` would be directly "observable" by the outer view – New Dev Jul 09 '20 at 22:25
  • Does this answer your question? [How to tell SwiftUI views to bind to nested ObservableObjects](https://stackoverflow.com/questions/58406287/how-to-tell-swiftui-views-to-bind-to-nested-observableobjects) – DI.dev Jul 10 '20 at 09:51
  • it doesn’t answer the question fully :) – M1X Jul 10 '20 at 18:37
  • If you can change ClientViewModel and ItemViewModel from classes to structs, your first approach will work. This off course depends on if you need them to be observableObject outside of this context. – guido Jul 27 '23 at 17:51

2 Answers2

4

Your first solution will not work as the changes to the nested ObservableObjects aren't propagated upwards:

class PurchaseViewModel: ObservableObject {
    let clientViewModel: ClientViewModel
    let itemsViewModel: ItemViewModel
    ...
}

A workaround can be found here: How to tell SwiftUI views to bind to nested ObservableObjects.


Your second approach is right and will work for most cases:

struct PurchaseView: View {
    @ObservedObject var purchaseViewModel: PurchaseViewModel
    @ObservedObject var itemViewModel: ItemViewModel
    @ObservedObject var clientViewModel: ClientViewModel
    ...
}

If you share an ObservableObject for many views you can inject it to the environment instead and access as an @EnvironmentObject.


Alternatively you can make your nested ObservableObjects to be structs:

class PurchaseViewModel: ObservableObject {
    @Published var clientViewModel: ClientViewModel // <- add `@Published`
    ...
}
struct ClientViewModel { // <- use `struct` instead of `class`
    ...
}

Note that your ClientViewModel will become a new struct every time it (or any of its properties) changes - this solution shouldn't be overused (especially for complex ViewModels).

pawello2222
  • 46,897
  • 22
  • 145
  • 209
  • It is weird to me this hasn't been fixed at all. Great variety of solutions though. One project I just mushed everything into a single Observed Object, functional but sloppy. Going to try the Struct solution and monitor the inefficiency of this. – C. Skjerdal Mar 10 '21 at 22:21
1

Just noting that I'm using the NestedObservableObject approach from How to tell SwiftUI views to bind to nested ObservableObjects.

Normally I'd avoid this but the nested object in question is actually a CoreData model so breaking things out into smaller views doesn't really work in this regard.

Since the world treats NSManagedObjects as (mostly) ObservableObjects, this solution seemed best.

Michael Long
  • 1,046
  • 8
  • 15