I have a SwiftUI List populated using a ForEach loop binded to a @Published array inside an ObservableObject
Elements in the list can be modified with an on/off flag. When the list is cleared out all items seems removed from the UI but when the data is refetched and loaded inside the @Published array the UI still shows the state of te previously modified elements. As you can see in the following video:
The code to reproduce it:
struct ListView: View {
@ObservedObject private var viewModel = ListViewModel()
var body: some View {
VStack {
List {
ForEach(viewModel.list, id: \.self) { element in
ElementRow(element: element)
}
}
HStack {
Button(action: { viewModel.fetch() }) { Text("Refresh") }
Button(action: { viewModel.remove() }) { Text("Remove") }
}
}.onAppear {
viewModel.fetch()
}
}
}
struct ElementRow: View {
@ObservedObject var element: ElementViewModel
var body: some View {
HStack {
Text(element.id)
Spacer()
Toggle("", isOn: $element.on )
}
}
}
class ListViewModel : ObservableObject {
@Published var list: [ElementViewModel] = []
func fetch() {
list.append(contentsOf: [ElementViewModel(id: "0", on: false), ElementViewModel(id: "1", on: false)])
}
func remove() {
list.removeAll()
}
}
class ElementViewModel : ObservableObject, Hashable {
var id: String = ""
@Published var on: Bool = false
init(id: String, on: Bool) {
self.id = id
self.on = on
}
static func == (lhs: ElementViewModel, rhs: ElementViewModel) -> Bool {
lhs.id == rhs.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(id)
}
}
What needs to be changed to make that all the elements in the list remains off when it's refreshed?
Your model objects is interpreted as equal, so views are not updated (List
caches rows similarly to UITableView
).
Find below a fix. Tested with Xcode 12.4 / iOS 14.4
class ElementViewModel : ObservableObject, Hashable {
// ... other code
static func == (lhs: ElementViewModel, rhs: ElementViewModel) -> Bool {
lhs.id == rhs.id && lhs.on == rhs.on // << here !!
}
// ... other code
}