Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my SwiftUI Text Field only allow you to change one character at a time?

Tags:

ios

swift

swiftui

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)

like image 647
Laura Edwards Avatar asked Dec 07 '25 06:12

Laura Edwards


1 Answers

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()
            }
        }
    }
}
like image 144
ytrewq Avatar answered Dec 09 '25 20:12

ytrewq



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!