Apr 16, 2024 iOS

How to detect live changes on TextField in SwiftUI?

In SwiftUI, you can detect live changes on a TextField using the .onChange modifier. Depending on the version of SwiftUI you’re using, there are different approaches:

  1. SwiftUI 2.0 (iOS 14, macOS 11, etc.): In SwiftUI 2.0, you can directly use the .onChange modifier to detect any change in the given state. Here’s an example:
struct ContentView: View {
    @State var location: String = ""

    var body: some View {
        TextField("Your Location", text: $location)
            .onChange(of: location) { newValue in
                // You can perform any action here based on the change.
                print(newValue)
                // For instance, call your `autocomplete(location)` function.
                // self.autocomplete(newValue)
            }
    }
}

SwiftUI 1.0 (older iOS versions): For older iOS versions and other SwiftUI 1.0 platforms, you can use .onReceive with the location.publisher to achieve similar behavior:

struct ContentView: View {
    @State var location: String = ""

    var body: some View {
        TextField("Your Location", text: $location)
            .onReceive(location.publisher) { newValue in
                // Handle the change here.
                print(newValue)
            }
    }
}
  1. Note that .onReceive returns the change itself, not the entire value. If you need behavior similar to .onChange, you can use the combine operator and follow the approach provided by @pawello2222 1.

Additionally, if you want to customize the behavior further, you can create a custom binding with a closure:

struct ContentView: View {
    @State var location: String = ""

    var body: some View {
        let binding = Binding<String>(
            get: { self.location },
            set: { self.location = $0 }
        )

        return VStack {
            Text("Current location: \(location)")
            TextField("Search Location", text: binding)
        }
    }
}

Remember that the didSet property observer doesn’t trigger when using a binding because the change occurs via location.wrappedValue, not directly on location 1