swiftswiftuifocustextfieldswiftui-navigationlink

Why does FocusState break when a view is pushed on the NavigationStack?


I have a view called TitleList that allows the user to edit the title of a row using a .swipeAction (it will replace the NavigationLink with a TextField).

While a title is being edited, if a user taps another row's NavigationLink and a view gets pushed on the root views NavigationStack, when I return to the root view and try to edit the previous row again, its TextField doesn't automatically activate despite its FocusState property being set to true (I have to tap the field for the keyboard to pop up). Here is a video that shows what I'm describing and also here is the full code:

import SwiftUI

struct HomeView: View {
    @State private var titles: [String] = ["Title 1", "Title 2", "Title 3"]

    var body: some View {
        NavigationStack {
            VStack {
                List {
                    TitleList(titles: $titles)
                }
            }
        }
    }
}

struct TitleList: View {
    @Binding var titles: [String]
    
    @State private var renamingTitleIndex: Int?
    @State private var renamingTextFieldUserInput: String = ""
    
    @FocusState private var renamingTextFieldIsFocused: Bool
    
    var body: some View {
        ForEach(Array(titles.enumerated()), id: \.offset) { index, title in
            if renamingTitleIndex != index {
                NavigationLink(destination: Text("Test")) {
                    Text(titles[index])
                }
                .swipeActions(allowsFullSwipe: false) {
                    Button {
                        renamingTextFieldIsFocused = true
                        renamingTitleIndex = index
                        renamingTextFieldUserInput = ""
                    } label: {
                        Label("Rename", systemImage: "pencil")
                    }
                }
            } else {
                VStack {
                    TextField("", text: $renamingTextFieldUserInput)
                        .focused($renamingTextFieldIsFocused)
                        .onSubmit {
                            if !renamingTextFieldUserInput.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                                titles[index] = renamingTextFieldUserInput
                            }
                        }
                        .onDisappear {
                            renamingTextFieldIsFocused = false
                        }
                        .onChange(of: renamingTextFieldIsFocused) { isFocused in
                            if !isFocused {
                                renamingTitleIndex = nil
                                renamingTextFieldUserInput = ""
                            }
                        }
                }
            }
        }
    }
}

Is there a way to get the FocusState property to work so I don't have manually tap the TextField when this happens? I know I could disable all the other rows while a row is editing (I probably will end up doing this regardless), but I'd also like to see if I can find a solution to this problem as I have other NavigationLinks in other Lists that could be triggered and would like to avoid having to disable all of them when a TitleList row is editing.


Solution

  • Add a delay when setting focus, this ensures the focus is set after the view is fully rendered.

    DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
        renamingTextFieldIsFocused = true
    }