Search code examples
swiftxcodeswiftuiswiftui-list

SwiftUI TextField not Refreshing on Variable Change


I have TextField inside a List inside a NavigationView, declared like this:

NavigationView {
    List {
        Section("Item Name") {
            TextField("", text: $itemName)
                .textInputAutocapitalization(.sentences)
                .disableAutocorrection(false)
                .textFieldStyle(.plain)
        }
    }
}

In the .onAppear method of the NavigationView, I set the textfield's text variable, which is declared with @State, like this:

@State var itemName: String = ""

and:

.onAppear {
    itemName = "Hello, World!"
}

The text is set properly, but the textfield somehow doesn’t refresh. I can tell because I can access the textfield's text property and get the updated value, and when the user taps the text field to edit it, the text suddenly appears.

It seems to me to be a problem with the textfield updating its view when the variable changes. Do I have to call a SwiftUI equivalent of UIKit's layoutSubviews? Or am I declaring my itemName variable wrong?

Minimal Reproducible Example (MRE):

struct ItemView: View {

    @State var itemName: String = ""

    var body: some View {
        NavigationView {
            List {
                Section("Item Name") {
                    TextField("", text: $itemName)
                        .textInputAutocapitalization(.sentences)
                        .disableAutocorrection(false)
                        .textFieldStyle(.plain)
                }
            }
            .navigationTitle("Item")
        }
        .onAppear {
            itemName = "Hello, World!"
        }
    }
}

Any help would be appreciated.


Solution

  • I also was able to reproduce it. I agree with Yrb, it seems like the state mutation in onAppear happens during the TextField's update and therefore the text field isn't picking up the new value from the state.

    Another way I was able to fix it is to move the state mutation from the NavigationView's .onAppear modifier to the TextField's .onAppear modifier. If you do that, you won't need DispatchQueue.main.async because SwiftUI will correctly synchronize the onAppear block's execution with the text field's update.