NavigationLink dismisses after TextField changes

Viewed 293

I have a navigation stack that's not quite working as desired.

From my main view, I want to switch over to a list view which for the sake of this example represents an array of strings. I want to then navigate to a detail view, where I want to be able to change the value of the selected string.

I have 2 issues with below code:

  1. on the very first keystroke within the TextField, the detail view is being dismissed
  2. the value itself is not being changed

Also, I suppose there must be a more convenient way to do the binding in the detail view ...

Here's the code:

import SwiftUI

@main
struct TestApp: App {
    var body: some Scene {
        WindowGroup {
            TestMainView()
        }
    }
}

struct TestMainView: View {
    var body: some View {
        NavigationView {
            List {
                NavigationLink("List View", destination: TestListView())
            }
            .navigationTitle("Test App")
        }
    }
}

struct TestListView: View {
    @State var strings = [
        "Foo",
        "Bar",
        "Buzz"
    ]
    
    @State var selectedString: String? = nil
    
    var body: some View {
        List(strings.indices) { index in
            NavigationLink(
                destination: TestDetailView(selectedString: $selectedString),
                tag: strings[index],
                selection: $selectedString) {
                Text(strings[index])
            }
            .navigationBarTitleDisplayMode(.inline)
            .navigationTitle("List")
        }
    }
}

struct TestDetailView: View {
    @Binding var selectedString: String?
    
    var body: some View {
        VStack {
            if let _ = selectedString {
                TextField("Placeholder",
                          text: Binding<String>( //what's a better solution here?
                            get: { selectedString! },
                            set: { selectedString = $0 }
                          )
                )
                .padding()
                .textFieldStyle(RoundedBorderTextFieldStyle())
            }
            Spacer()
        }
        .navigationTitle("Detail")
    }
}

struct TestMainView_Previews: PreviewProvider {
    static var previews: some View {
        TestMainView()
    }
}

I am quite obviously doing it wrong, but I cannot figure out what to do differently...

1 Answers

You're changing the NavigationLink's selection from inside the NavigationLink which forces the TestListView to reload.

You can try the following instead:

struct TestListView: View {
    @State var strings = [
        "Foo",
        "Bar",
        "Buzz",
    ]

    var body: some View {
        List(strings.indices) { index in
            NavigationLink(destination: TestDetailView(selectedString: self.$strings[index])) {
                Text(self.strings[index])
            }
        }
    }
}

struct TestDetailView: View {
    @Binding var selectedString: String // remove optional

    var body: some View {
        VStack {
            TextField("Placeholder", text: $selectedString)
                .padding()
                .textFieldStyle(RoundedBorderTextFieldStyle())
            Spacer()
        }
    }
}
Related