Hello, following is the issue: I have a @Observable view model which has an array of @Observable Items.
Tapping an item leads to a detail like view to submit a value to the item. This work thru bindings. However I have the need to replace the contents of the array entirely with a fresh version loaded from the network. It will contain the "same" objects with the same id but some values might have changed. So replacing the entire array seems to not update the UI because the IDs are the same as before. Also this seems to break the bindings because when replacing the array, editing no longer updates the UI.
How to test the behavior:
- Launch the app in simulator.
- Add some values to the items by tapping on an item and then on add.
- Notice how changes are updated.
- Tap the blue button to sync fresh data to the array. (Not replacing the actual array)
- Confirm everything is still working
- Replace the array with the red button.
- Editing and UI updates are broken from now on.
What is the proper way to handle this scenario?
Project: https://github.com/ChristianSchuster/DTS_DataReplaceExample.git
I believe the reason SwiftUI doesn't update the list is because the items have no change based on your implementation. Look at your following code:
@Observable
final class MyObservableItem: Codable, Identifiable, Hashable, Equatable {
...
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
static func == (lhs: MyObservableItem, rhs: MyObservableItem) -> Bool {
return lhs.id == rhs.id
}
This Hashable
implementation tells Swift that items with the same id
are the same. With this, when you render items with the following code:
ForEach($model.items, id: \.self) { $item in
SwiftUI checks the hashes of the items (because you specify .self
for the id
parameter) and finds no change (because item.id
isn't changed), and hence determines that no update is needed.
To address this issue, you may re-consider the equality of the items. For example, if items that have the same id
but different currentValue
are not the same, you can implement Hashable
in the following way:
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(currentValue)
}
static func == (lhs: MyObservableItem, rhs: MyObservableItem) -> Bool {
//return lhs.id == rhs.id
return lhs.id == rhs.id && lhs.currentValue == rhs.currentValue
}
With this, SwiftUI should update the list when currentValue
changes.
Best,
——
Ziqiao Chen
Worldwide Developer Relations.