Following on from this thread: https://developer.apple.com/forums/thread/805037 my list of items is now correctly maintaining state (no more disappearing rows), but I'm now hitting a really annoying issue: Every time something changes - even just changing the dark mode of the device - the entire list of items is refreshed, and the list jumps back to the top.
A simple representation:
// modelData.filteredItems is either all items or some items, depending on whether the user is searching
List {
ForEach(modelData.filteredItems) { item in
ItemRow(item: item)
}
}
When the user isn't searching, filteredItems has everything in it. When they turn on search, I filter and sort the data in place:
// Called when the user turns on search, or when the searchString or searchType changes
func sortAndFilterItemsInModelData() {
modelData.filteredItems.removeAll() // Remove all items from the filtered array
modelData.filteredItems.append(contentsOf: modelData.allItems) // Add all items back in
let searchString: String = modelData.searchString.lowercased()
switch(modelData.searchType) {
case 1:
// Remove all items from the filtered array that don't match the search string
modelData.filteredItems.removeAll(where: { !$0.name.lowercased().contains(searchString) })
...
}
// Sorting
switch(modelData.sortKey) {
case sortKeyDate:
modelData.sortAscending ? modelData.filteredItems.sort { $0.date < $1.date } : modelData.filteredItems.sort { $0.date > $1.date } // Sorts in place
...
}
}
The method doesn't return anything because all the actions are done in place on the data, and the view should display the contents of modelData.filteredItems.
If you're searching and there are, say 10 items in the list and you're at the bottom of the list, then you change the search so there are now 11 items, it jumps back to the top rather than just adding the extra ItemRow to the bottom. Yes, the data is different, but it hasn't been replaced; it has been altered in place.
The biggest issue here is that you can simply change the device to/from Dark Mode - which can happen automatically at a certain time of day - and you're thrown back to the top of the list. The array of data hasn't changed, but SwiftUI treats it as though it has.
There's also a section in the List that can be expanded and contracted. It shows or hides items of a certain type. When I expand it, I expect the list to stay in the same place and just show the extra rows, but again, it jumps to the top. It's a really poor user experience.
Am I doing something wrong (probably, yes), or is there some other way to retain the scroll position in a List? The internet suggests switching to a LazyVStack, but I lose left/right swipe buttons and the platform-specific styling.
Thanks.