I have a simple master/detail interface where the detail view modifies an item in an array. Using the below, the model is updated properly, but SwiftUI doesn't refresh the View to reflect the change.
Model:
struct ProduceItem: Identifiable {
let id = UUID()
let name: String
var inventory: Int
}
final class ItemStore: BindableObject {
var willChange = PassthroughSubject<Void, Never>()
var items: [ProduceItem] { willSet { willChange.send() } }
init(_ items: [ProduceItem]) {
self.items = items
}
}
Master view that displays a list of ProduceItems (an ItemStore is inserted into the environment in the SceneDelegate):
struct ItemList: View {
@EnvironmentObject var itemStore: ItemStore
var body: some View {
NavigationView {
List(itemStore.items.indices) { index in
NavigationLink(destination: ItemDetail(item: self.$itemStore.items[index])) {
VStack(alignment: .leading) {
Text(self.itemStore.items[index].name)
Text("\(self.itemStore.items[index].inventory)")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
.navigationBarTitle("Items")
}
}
}
Detail view that lets you change the inventory value of an item:
struct ItemDetail: View {
@Binding var item: ProduceItem
var body: some View {
NavigationView {
Stepper(value: $item.inventory) {
Text("Inventory is \(item.inventory)")
}
.padding()
.navigationBarTitle(item.name)
}
}
}
Tapping on the stepper in the ItemDetail view modifies the item in the store, but the text of the stepper doesn't change. Navigating back to the list confirms the model has been changed. Also, I confirmed that the store calls willChange.send()
to its publisher. I would assume that the send()
call updates the ItemStore in the environment and the detail view's @Binding
property should be notified of the change and refresh the display (but it doesn't).
I tried changing ItemDetail's item property to use @State
:
@State var item: ProduceItem = ProduceItem(name: "Plums", inventory: 7)
In this case, the model is item is updated when using the stepper and the view is refreshed, displaying the updated inventory. Can anyone explain why using the @Binding
property doesn't refresh the interface, but a local @State
property does?
Here you have a workaround. Use the index, instead of the element when calling ItemDetail. And inside ItemDetail, you use the @EnvironmentObject
.
struct ItemList: View {
@EnvironmentObject var itemStore: ItemStore
var body: some View {
NavigationView {
List(itemStore.items.indices) { index in
NavigationLink(destination: ItemDetail(idx: index)) {
VStack(alignment: .leading) {
Text(self.itemStore.items[index].name)
Text("\(self.itemStore.items[index].inventory)")
.font(.caption)
.foregroundColor(.secondary)
}
}
}
.navigationBarTitle("Items")
}
}
}
struct ItemDetail: View {
@EnvironmentObject var itemStore: ItemStore
let idx: Int
var body: some View {
NavigationView {
Stepper(value: $itemStore.items[idx].inventory) {
Text("Inventory is \(self.itemStore.items[idx].inventory)")
}
.padding()
.navigationBarTitle(itemStore.items[idx].name)
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With