Search code examples
swiftswiftuinavigation

SwiftUI state management of lists and navigation


I have a list of things (TestSettings). I use the @State annotation. I drill down and can edit the items. In the drill-down, I use @Binding as I want it to change the original rather than a copy.

However, as soon as I edit the field in the second level, it pops back to the top level. For example, with the following code, I tap the top entry in the list and I see "A". Tap in the text box, add another "A" and it immediately pops back to the list. If I go back it has remembered the change ("AA").

What am I doing wrong?

struct TestSettings : Identifiable, Hashable {
    var name : String

    var id : String { self.name }
}

struct SecondLevel : View {
    @Binding var things : TestSettings

    var body : some View {
        List {
            TextField("Name", text: $things.name)
        }
    }
}

struct PickerTest: View {
    @State var settings : [TestSettings]

    var body: some View {
        NavigationView {
            List {
                ForEach($settings, id: \.self) { thing in
                    NavigationLink(destination: SecondLevel(things: thing))  {
                        Text("X")
                    }
                }
            }
            .navigationTitle("Test")
        }
    }
}

struct PickerTest_Previews: PreviewProvider {
    static var previews: some View {
        PickerTest(settings: [TestSettings(name: "A"), TestSettings(name: "B")])
    }
}

Solution

  • You change thing which is ID of ForEach (by .self), so once you changed it that thing actually disappeared (for List) and new one appeared, so List's content updated.

    A possible solution is to have persistent (separated) id of TestSettings so editing it would not affect its identity.

    Like next

    struct TestSettings : Identifiable, Hashable {
        var name : String
    
        var id = UUID()   // << here !!
    }
    
    // ...
    
        // identifiable, os explicit key-path is not needed, `id` used by default
        ForEach($settings) { thing in    // << here !!
            NavigationLink(destination: SecondLevel(things: thing))  {
                Text("X")
            }
        }