swiftuiviewrestore

Select an item of list in NavigationStack for view restoration


In my app there's a list in a NavigationStack for selecting an object for the next view. This is working well. But after restarting the app the selected item should be preselected. I've tried the following code:

@AppStorage("selectedIndex") private var selectedIndex: Int?

var body: some View {
   NavigationStack {
      List(selection: $selectedIndex) {
         ForEach(0..<myObjectsArray.count, id: \.self) { index in
            NavigationLink(destination: MyObjectView(selectedObject: myObjectsArray[index]) {
               VStack(alignment: .leading) {
                  HStack{
                     Text(myObjectsArray[index].name)
                     Text("- \(myObjectsArray[index].numbers) Employees")
                   }
                   Text("last Update: \(myObjectsArray[index].updated.ISO8601Format())")
                      .font(.footnote)
               }
            }
         }      
      }
   }
}

After restart of my app, the item of selectedIndex is highlighted but the corresponding view isn't shown. Where's my error?


Solution

  • Try this approach using NavigationPath and NavigationLink(value: index) as shown in the example code. Note you should not use index etc.. in the ForEach loop, use the actual Item.

    
    // for testing
    struct TheItem: Identifiable, Hashable {
        let id = UUID()
        var name: String
        var numbers: Int
    }
    
    struct ContentView: View {
        var body: some View {
            SelectionMode()
        }
    }
    
    struct SelectionMode: View {
        @AppStorage("selectedIndex") private var selectedIndex: Int?
        
        // for testing
        @State private var myObjectsArray = [
            TheItem(name: "item-1", numbers: 11),
            TheItem(name: "item-2", numbers: 22)
        ]
        
        @State private var path = NavigationPath() // <--- here
        
        var body: some View {
            NavigationStack(path: $path) {
                List {
                    ForEach(0..<myObjectsArray.count, id: \.self) { index in
                        NavigationLink(value: index) {  // <--- here
                            VStack(alignment: .leading) {
                                HStack{
                                    Text(myObjectsArray[index].name)
                                    Text("- \(myObjectsArray[index].numbers) Employees")
                                }
                                Text("last Update)")
                                    .font(.footnote)
                            }
                        }
                    }
                }
                .navigationDestination(for: Int.self) { index in  // <--- here
                    MyObjectView(selectedObject: myObjectsArray[index])
                        .onAppear {
                            selectedIndex = index
                        }
                }
            }
            .onAppear {
                if let ndx = selectedIndex {
                    path.append(ndx)  // <--- here
                }
            }
        }
    }
      
    struct MyObjectView: View {
        let selectedObject: TheItem
        
        var body: some View {
            Text("MyObjectView \(selectedObject.name)")
        }
    }
    

    EDIT-1

    Another more robust approach without using index, is to store the id of the item instead of the index.

    For example:

    
    struct TheItem: Identifiable, Hashable {
        let id: Int  // <--- here
        var name: String
        var numbers: Int
    }
    
    struct ContentView: View {
        @AppStorage("selectedItem") private var selectedItem: Int?
        
        // for testing
        @State private var myObjectsArray = [
            TheItem(id: 0, name: "item-0", numbers: 0),
            TheItem(id: 1,name: "item-1", numbers: 1)
        ]
        
        @State private var path = NavigationPath() // <--- here
        
        var body: some View {
            NavigationStack(path: $path) {
                List {
                    ForEach(myObjectsArray) { item in  // <--- here
                        NavigationLink(value: item) {  // <--- here
                            VStack(alignment: .leading) {
                                HStack {
                                    Text(item.name)
                                    Text("- \(item.numbers) Employees")
                                }
                                Text("last Update)").font(.footnote)
                            }
                        }
                    }
                }
                .navigationDestination(for: TheItem.self) { item in  // <--- here
                    MyObjectView(selectedObject: item)
                        .onAppear {
                            selectedItem = item.id
                        }
                        .onDisappear {
                            selectedItem = -1 // <--- if required
                        }
                }
            }
            .onAppear {
                if let item = myObjectsArray.first(where: {$0.id == selectedItem ?? -1}) {
                    path.append(item)  // <--- here
                }
            }
        }
    }
       
    struct MyObjectView: View {
        let selectedObject: TheItem
        
        var body: some View {
            Text("MyObjectView: \(selectedObject.name)  \(selectedObject.numbers)")
        }
    }