I'm trying to create a view for a specific birthday/date in my app, and I've used text fields so the user can change existing ideas. However the text field only allows you to change one character at a time? Then it kicks the user out of typing (they can then click on the text field again and change another character). Ideally they should be able to keep typing until they click off.
This should be all the relevant code as I don't think anything else should affect it:
@State private var exampleIdeas = ["Sweets", "Chocolate", "Cake"]
ForEach($exampleIdeas, id:\.self, editActions: .delete) { $idea in
TextField("Idea", text: $idea)
}
I've tried looking for previous solutions but no-one else seems to be having this problem? (I don't know of any possible causes / solutions myself)
As ForEach help says:
It’s important that the id of a data element doesn’t change unless you replace the data element with a new data element that has a new identity. If the id of a data element changes, the content view generated from that data element loses any current state and animations.
So when you adding/changing/deleting a character from the word in the TextField, you are changing the id of that word (since you are using id:\.self). And the whole content is regenerated.
Either you need to use something else for an id, not self (i.e. have a class or struct that contains id that doesn't change, and your string that may change), or you can have a @State for edited element and only update original values when user is done editing.
Here's the fix using the first method (fixing the id problem):
class Idea {
let id = UUID() // <-- unchangeable
var value: String
init(_ value: String) {
self.value = value
}
}
struct MyView: View {
@State private var exampleIdeas = [
Idea("Sweets"),
Idea("Chocolate"),
Idea("Cake")
]
var body: some View {
VStack(spacing: 0) {
ForEach($exampleIdeas,
id:\.self.id, // <-- id won't change with editing of the value
editActions: .delete) { $idea in
TextField("Idea", text: $idea.value)
}
}
}
}
I recommend the above method, as it's simpler. But here's also the method that allows to keep the binded variable intact while typing, and only update it on commit or when moving focus to another field. In this case exampleIdeas don't need to change, but we introduce a new custom UI component MyTextField:
struct MyTextField: View {
private let label: String
@State var changingText: String
@Binding var finalText: String
init(_ label: String, text: Binding<String>) {
self.label = label
_finalText = text
changingText = text.wrappedValue
}
var body: some View {
TextField("Idea", text: $changingText, onEditingChanged: { (changed) in
if !changed { // <-- changed focus to another field
finalText = changingText
}
})
.onSubmit { // <-- submitted
finalText = changingText
}
}
}
struct MyView: View {
@State private var exampleIdeas = ["Sweets", "Chocolate", "Cake"]
var body: some View {
VStack(spacing: 0) {
ForEach($exampleIdeas, id:\.self, editActions: .delete) { $idea in
MyTextField("Idea", text: $idea)
Text(idea).italic()
}
}
}
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With