Post

Replies

Boosts

Views

Activity

Reply to List with Sections: Odd Behaviour when item in list changes
I found a solution using the tag property of the NavigationLink and @SceneStorage property wrapper. Create a @SceneStorage (persistent state per scene) @State private var sceneItemID: String? or @SceneStorage private var sceneItemID: String? Add a tag with the unique id of the item to every NavigationLink NavigationLink(destination: DetailView(item: $item), tag: item.id, selection: $sceneItemID, label: { RowView(item: $item) }) Every time the Navigation Link is used the sceneItemID is updated with the tag (item.id in this case). In the DetailView update the sceneItemID in the .onAppear() modifier. This is necessary due to the behaviour during state change of isFav. Now it is working only on iPad the Sidebar does not correctly display the selection. On macOS and iPhone this works. // // ContentView.swift // Shared // // Created by Christian on 06.06.21. // import SwiftUI //MARK: - Data Model struct Item: Identifiable, Equatable, Hashable { var id = UUID().uuidString var isFav = false var text: String } struct ItemScoped: Identifiable, Equatable, Hashable { var id: String { return item.id } var item: Item var index: Int } //MARK: Store class ItemStore: ObservableObject { @Published var items = [Item(id: "uuid01", text: "Item 1"), Item(id: "uuid02", text: "Item 2"), Item(id: "uuid03", isFav: true, text: "Item 3"), Item(id: "uuid04", text: "Item 4")] /// scope item to sections and keep knowledge of origin index func scopedItems(isFav: Bool) -> [ItemScoped] { let sItems: [ItemScoped] = items.compactMap { guard let idx = items.firstIndex(of: $0) else { return nil } //find(items, $0) return ItemScoped(item: $0, index: idx) } return sItems.filter { $0.item.isFav == isFav } } } //MARK: - Views struct ContentView: View { // usally this is @EnvironmetObject, due to simplicity I put it here @StateObject var store: ItemStore = ItemStore() @SceneStorage("SceneItemSelectionID") private var sceneItemID: String? var body: some View { NavigationView { List { Section(header: Text("Favorites")) { ForEach(store.scopedItems(isFav: true)) { scopedItems in NavigationLink( destination: DetailView(item: $store.items[scopedItems.index]), //MARK: !! IMPORTANT: use unique indetifier as tag tag: store.items[scopedItems.index].id, selection: $sceneItemID, label: { RowView(item: $store.items[scopedItems.index]) }) } } Section(header: Text("Others")) { ForEach(store.scopedItems(isFav: false)) { scopedItems in NavigationLink( destination: DetailView(item: $store.items[scopedItems.index]), //MARK: !! IMPORTANT: use unique indetifier as tag tag: store.items[scopedItems.index].id, selection: $sceneItemID, label: { RowView(item: $store.items[scopedItems.index]) }) } } } .listStyle(SidebarListStyle()) .navigationTitle("Items") } } } // MARK: Row View /// RowView for item, tapping the text toggle the `isFav` state struct RowView: View { @Binding var item: Item var body: some View { Label( title: { Text(item.text) }, icon: { item.isFav ? Image(systemName: "star.fill") : Image(systemName: "star")} ) } } // MARK: Detail View /// DetailView to change item `text` and toggle `isFav` state struct DetailView: View { @Binding var item: Item @SceneStorage("SceneItemSelectionID") private var sceneItemID: String? var body: some View { VStack { Spacer() .frame(height: 20.0) TextField("Title", text: $item.text) .background(Color.gray.opacity(0.2)) .padding(10) Toggle("is Fav:", isOn: $item.isFav.animation()) .padding() Spacer() } .padding() .onAppear() { //MARK: !! IMPORTANT set scene selction id again sceneItemID = item.id } } } // MARK: - Preview struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
Jun ’21