How to get notified for any change in SwiftUI TextField?

I have a UI that needs to be updated as the user makes changes in a SwiftUI TextField.


How do I get notified for all changes made?


The onEditingChanged is only called when te text is changed for the first time after being commited, and this does not change the binding text.

Accepted Reply

I think the WWDC talk was Combine In Practice, but that example used UIKit.


The thing to remember is that in SwiftUI, views are a function of state, nothing more. Your state is what drives everything—you can't get things from a View instance, you have to bind the view to some state, then use the state to influence your decisions. In keeping with that, you would bind your text field to some state and then use that state in your view rendering code.


For your simple example, you could set your text field to just use the `input` variable directly to display what's being typed. If you specifically want to assign the final result to a different variable, you can use the `onCommit` handler to do that. Meanwhile, the `onEditingChanged` handler isn't referring to 'the user edited the field so its value changed', it means 'the "isEditing" property of the text field has changed' — it hands you a Bool telling you whether it's now editing or not.


A version of your simple example that does exactly what you describe appears below; note the use of a third state variable used to determine whether to print "You are typing:" or "You typed:" based on the `isEditing` property of the text field:


import SwiftUI

struct ContentView: View {
    @State var output: String = ""
    @State var input: String = ""
    @State var typing = false
    var body: some View {
        VStack {
            if !typing {
                if !output.isEmpty {
                    Text("You typed: \(output)")
                }
            } else if !input.isEmpty {
                Text("You are typing: \(input)")
            }
            TextField("", text: $input, onEditingChanged: {
                self.typing = $0
            }, onCommit: {
                self.output = self.input
            })
            .background(Color.green.opacity(0.2))
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Replies

changed() is equivalent to beginEditing, not didChange.


Could you show the code where you define the change and commit handlers ?

May be you could force a commit in the changed() handler ?

And file a request for enhancement.

Here is a view that shows my problem:

struct TestView: View {
  @State var output: String = ""
  @State var input: String = ""
  var body: some View {
    VStack(spacing: -1.0) {
      Text(output)
      TextField("", text: $input, onEditingChanged: { changed in
        print("Changed")
        self.output = "You are typing: " + self.input
      }, onCommit: {
        print("Commited")
        self.output = "You typed: " + self.input
      })
    }
  }
}


As soon as you start typing, the output shows "You are typing: " but with the last commited input.

As soon as you press enter, it shows "You typed: " with the full input of the textField.


I would like "You are typing: " to update as the text field is chaning.


I vaguely recall seeing it in one of the WWDC19 presentations where this was done with a search field, but I cannot seem to find it.

The idea (but I cannot test it) would be to call commit handler from within onEditingChanged handler.

Sorry, I could not find the syntax to do it (if possible)


This could hint at some solution to achieve this:


https://stackoverflow.com/questions/56476007/swiftui-textfield-max-length

I think the WWDC talk was Combine In Practice, but that example used UIKit.


The thing to remember is that in SwiftUI, views are a function of state, nothing more. Your state is what drives everything—you can't get things from a View instance, you have to bind the view to some state, then use the state to influence your decisions. In keeping with that, you would bind your text field to some state and then use that state in your view rendering code.


For your simple example, you could set your text field to just use the `input` variable directly to display what's being typed. If you specifically want to assign the final result to a different variable, you can use the `onCommit` handler to do that. Meanwhile, the `onEditingChanged` handler isn't referring to 'the user edited the field so its value changed', it means 'the "isEditing" property of the text field has changed' — it hands you a Bool telling you whether it's now editing or not.


A version of your simple example that does exactly what you describe appears below; note the use of a third state variable used to determine whether to print "You are typing:" or "You typed:" based on the `isEditing` property of the text field:


import SwiftUI

struct ContentView: View {
    @State var output: String = ""
    @State var input: String = ""
    @State var typing = false
    var body: some View {
        VStack {
            if !typing {
                if !output.isEmpty {
                    Text("You typed: \(output)")
                }
            } else if !input.isEmpty {
                Text("You are typing: \(input)")
            }
            TextField("", text: $input, onEditingChanged: {
                self.typing = $0
            }, onCommit: {
                self.output = self.input
            })
            .background(Color.green.opacity(0.2))
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

Thanks, that makes perfect sense, and I just did not think about it that way - that the input IS the textField value.