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.
CodePudding user response:
It worked fine in Preview, but I took the next step and tested it in the Simulator, and it failed. That lead me to realize what the issue was. We see this a great deal with animations that are started from .onAppear()
. The view is set up and .onAppear()
called before the view is actually on screen, causing a failure to update from the .onAppear()
. Think of it almost as a race condition. The view has to be set up and on screen before the update can be called, and it needs an extra cycle to do this. As a result the fix is simple:
.onAppear {
DispatchQueue.main.asyncAfter(deadline: .now()){
itemName = "Hello, World!"
}
}
Wrap it in a DispatchQueue.main.asyncAfter(deadline:)
. This gives it time to avoid the issue.
CodePudding user response:
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.