In SwiftUI, how do you limit which @Published properties of an ObservableObject trigger a view to be refreshed?

Viewed 433

In SwiftUI, is there a way to control which @Published properties in an @ObservableObject trigger a view to be refreshed?

On the iPad and macOS, I find it common to share a model between multiple views that are presented on the screen at the same time. This model is an ObservableObject with numerous properties that are @Published. However, a chance to any of those properties appears to cause SwiftUI to rebuild the view hierarchy, even if not entirely necessary.

Consider the example below. There is a model called Order that has several published properties. There are two views presented side-by-side that both depend on Order. If you make a change to Order.name or Order.address from the CustomerView, then that triggers the CartView to be refreshed as well, even though it is only dependent on Order.items.

Ideally, I want a way to tell CustomerView to ignore updates to Order.items and to tell CartView to ignore updates to Order.name and Order.address. But since order is an @EnvironmentObject I can't figure out how to do that.

Alternative Solution:

One way I could work around this to not use @EnvironmentObject and instead just pass in the Order through a standard property initializer. Then declare separate @Binding properties on CustomerView and CartView for managing the state. This works, but quickly becomes a lot of duplication if the views and models have a lot of properties.

Not like it matters too much, but this is mostly for iPadOS and macOS development.

┌──────────────────────────────────────────┐
│               ContentView                │
│                                          │
│┌──────────────────┐ ┌──────────────────┐ │
││                  │ │                  │ │
││   CustomerView   │ │      CartView    │ │
││                  │ │                  │ │
││                  │ │                  │ │
│└──────────────────┘ └──────────────────┘ │
└──────────────────────────────────────────┘



final class Order: ObservableObject {
  @Published var name: String
  @Published var items: [Item]
  @Published var address: String
}

struct ContentView: View {
  @StateObject var order = Order()
  
  var body: some View {
    HStack {
      CustomerView()
      CartView()
    }.environmentObject(order)
  }
}

struct CustomerView: View {
  @EnvironmentObject var order: Order
  
  // This view gets rebuilt when 'order.items' is changed.
  var body: some View {
    VStack {
      TextField("Name", text: $order.name)
      TextField("Address", text: $order.address)
    }
  }
}

struct CartView: View {
  @EnvironmentObject var order: Order
  
  // This view gets rebuilt when 'order.name' or 'order.address' is changed.
  var body: some View {
    List(order.items, id: \.id) { item in
      ItemView(item)
    }
  }
}
0 Answers
Related