I have a very simple List with some Sections, in the same view I have also one button which will become enabled when any of list items are selected, this is controlled with a State variable, when this is happening, if list is scrolled down, State variable will change (to enable the button) and all view will refresh, causing my list to scroll on top. How can I avoid this scroll reset, I should mention that the same is happening if elements are removed or added to the list, however, I tried to simplify the problem as much as possible, here is the simplified code snippet.
import SwiftUI
enum FavoritesListActiveSheet: Identifiable {
case moveToSheet
var id: Int { hashValue }
}
struct Category: Identifiable {
var id: UUID = UUID()
var name: String
}
extension Category {
static var categories: [Category] {
[
Category(name: "category 1"),
Category(name: "category 2")
]
}
}
struct FavoriteItem: Identifiable {
var id: UUID = UUID()
var name: String
var category: String
var selected: Bool
}
extension FavoriteItem {
static var favoriteItems: [FavoriteItem] {
[
FavoriteItem(name: "Item 1", category: "category 1", selected: false),
FavoriteItem(name: "Item 2", category: "category 1", selected: false),
FavoriteItem(name: "Item 3", category: "category 1", selected: false),
FavoriteItem(name: "Item 4", category: "category 2", selected: false),
FavoriteItem(name: "Item 5", category: "category 2", selected: false),
FavoriteItem(name: "Item 6", category: "category 2", selected: false),
FavoriteItem(name: "Item 7", category: "category 2", selected: false),
FavoriteItem(name: "Item 8", category: "category 2", selected: false),
FavoriteItem(name: "Item 9", category: "category 2", selected: false),
FavoriteItem(name: "Item 10", category: "category 2", selected: false),
FavoriteItem(name: "Item 11", category: "category 2", selected: false),
FavoriteItem(name: "Item 12", category: "category 2", selected: false),
FavoriteItem(name: "Item 13", category: "category 2", selected: false),
FavoriteItem(name: "Item 14", category: "category 2", selected: false),
FavoriteItem(name: "Item 15", category: "category 2", selected: false),
FavoriteItem(name: "Item 16", category: "category 2", selected: false),
FavoriteItem(name: "Item 17", category: "category 2", selected: false),
FavoriteItem(name: "Item 18", category: "category 2", selected: false),
]
}
}
struct FavoritesRaw: View {
@Binding var item: FavoriteItem
@State var refreshView: Bool = false
let onItemToggle: () -> ()
var body: some View {
HStack {
if (item.selected) {
Image(systemName: "checkmark.circle")
} else {
Image(systemName: "circle")
}
Text (item.name)
}
.simultaneousGesture(TapGesture().onEnded {
self.item.selected.toggle()
refreshView.toggle()
onItemToggle()
})
.contentShape(Rectangle())
}
}
class FavoritesViewModel: ObservableObject {
var favorite_items: [FavoriteItem] = FavoriteItem.favoriteItems
}
struct FavoritesListView: View {
@StateObject var viewModel: FavoritesViewModel = FavoritesViewModel()
@State var addtoButtonDisabled: Bool = true
@State var sheetDisplayed: FavoritesListActiveSheet?
var body: some View {
NavigationView {
VStack {
List {
ForEach (Category.categories) { category in
Section (header: Text(category.name))
{
ForEach (self.viewModel.favorite_items.filter({$0.category == category.name})) { item in
FavoritesRaw(item: binding(for: item), onItemToggle: {
addtoButtonDisabled = (self.viewModel.favorite_items.filter({$0.selected == true}).count == 0)
})
}
}
.textCase(nil)
}
}
.listStyle(PlainListStyle())
.id(UUID()) // no animation
}
.navigationBarTitle("Favorites", displayMode: .inline)
.toolbar {
ToolbarItemGroup(placement: .navigationBarTrailing) {
Button(action: {
sheetDisplayed = .moveToSheet
}) {
Text("Add to...")
}.disabled(addtoButtonDisabled)
}
}
.sheet(item: $sheetDisplayed) { item in
// [Show a sheet then disable back the button]
}
}
.onAppear
{
addtoButtonDisabled = (self.viewModel.favorite_items.filter({$0.selected == true}).count == 0)
}
}
private func binding(for item: FavoriteItem) -> Binding<FavoriteItem> {
guard let item_index = self.viewModel.favorite_items.firstIndex(where: { $0.id == item.id }) else {
fatalError("Can't find item in array")
}
return $viewModel.favorite_items[item_index]
}
}
It seems that removing
.id(UUID()) // no animation
is fixing my problem. However, I've added this to get rid of the ugly animation, SwiftUI is providing on element delete.