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)
}
}
}