Why is didSet called twice on the TextField binding in SwiftUI?

1.6k Views Asked by At

I have a very basic view that only shows a TextField:

View

struct ContentView: View {

    @StateObject var viewModel = ViewModel()
    
    var body: some View {
        TextField("Enter a string...", text: $viewModel.string)
    }
    
}

The TextField's text is bound to a string property on the view model:

ViewModel

class ViewModel: ObservableObject {
    
    @Published var string: String = "" {
        didSet {
            print("didSet string:", string)
        }
    }
    
}

I added a didSet property observer to perform a custom action whenever the string changes. For this simple example, I only print a string on the console.

Observation

When I run this code and enter the string "123" into the text field, this is the output I get:

didSet string: 1
didSet string: 1
didSet string: 12
didSet string: 12
didSet string: 123
didSet string: 123

Question:

Why?
Why is the didSet closure called twice for each character I type? (I would expect it to be called once for each character.)

Is there anything wrong with the code or is this expected behavior somehow?

3

There are 3 best solutions below

0
العبد On
 let binding = Binding<String>(get: {
                textvariable
            }, set: {
                viewModel.setText(query: $0) //add event inside setText
                // do whatever you want here
            })
4
sahandnayebaziz On

I’m seeing this issue on Xcode 14.2 RC and iOS 16.2 RC, but weirdly what fixes it is adding a .textFieldStyle(.plain) or .textFieldStyle(.roundedBorder).

I’m really not sure why having no textFieldStyle would affect this, but the binding calls set:{} twice when I have no textFieldStyle set, and as soon as I add one of those, it behaves normally and only calls set:{} once at a time.

I hope this helps someone!

0
Cristi Băluță On

There is an operator .removeDuplicates() that you can put before the sink and it will publish only unique values. The problem with .textFieldStyle is that you still get extra calls when the textfield loses focus, or at least on macOS.