22

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?

Mischa
  • 15,816
  • 8
  • 59
  • 117
  • 1
    I recall in SwiftUI 1 or 2, it was not called at all. If you have sensitive side-effect then try to use property publisher/combine. – Asperi Jan 17 '22 at 06:10
  • 1
    Interesting. What do you mean by using a property publisher? Listening to changes via `$string.sink {...}`? – Mischa Jan 17 '22 at 10:44
  • 2
    I'm experiencing the same issue and I don't know why this is happening. I know didSet + property wrappers were behaving weird in Swift. Maybe it's a bug? https://forums.swift.org/t/swift-5-2-struct-property-wrapper-didset-defect/34403/5 – Ilea Cristian Mar 24 '22 at 13:54
  • I have the exact same issue. Even with combine .. .$string.sink {...} the value arrives two times. – Stefan Nestorov Apr 06 '22 at 18:59
  • 1
    I am also having this issue, in my case the textfield also queries my database whenever a new character is typed (to autocomplete the entry) so I'm making twice the necessary calls. Has anybody started a ticket with apple, or made a post in the swift forums concerning this? – Karrsen B Apr 11 '22 at 06:32
  • Couple of possible solutions/workarounds here: https://forums.swift.org/t/why-published-var-didset-is-called-extra-time-when-its-referenced-by-textfield-binding/52940/7 – Robin Macharg Jun 11 '22 at 13:18

2 Answers2

10

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!

sahandnayebaziz
  • 622
  • 7
  • 20
-1
 let binding = Binding<String>(get: {
                textvariable
            }, set: {
                viewModel.setText(query: $0) //add event inside setText
                // do whatever you want here
            })
العبد
  • 359
  • 5
  • 15