12

I'm trying to execute an action every time a textField's value is changed.

@Published var value: String = ""

var body: some View {            
     $value.sink { (val) in
        print(val)
     }
     return TextField($value)       
}

But I get below error.

Cannot convert value of type 'Published' to expected argument type 'Binding'

RealHowTo
  • 34,977
  • 11
  • 70
  • 85
Sorin Lica
  • 6,894
  • 10
  • 35
  • 68

6 Answers6

23

This should be a non-fragile way of doing it:

class MyData: ObservableObject {
    var value: String = "" {
        willSet(newValue) {
            print(newValue)
        }
    }
}

struct ContentView: View {
    @ObservedObject var data = MyData()
    var body: some View {
        TextField("Input:", text: $data.value)
    }
}
valexa
  • 4,462
  • 32
  • 48
8

In your code, $value is a publisher, while TextField requires a binding. While you can change from @Published to @State or even @Binding, that can't observe the event when the value is changed.

It seems like there is no way to observe a binding.

An alternative is to use ObservableObject to wrap your value type, then observe the publisher ($value).

class MyValue: ObservableObject {
  @Published var value: String = ""
  init() {
    $value.sink { ... }
  }
}

Then in your view, you have have the binding $viewModel.value.

struct ContentView: View {
    @ObservedObject var viewModel = MyValue()
    var body: some View {
        TextField($viewModel.value)
    }
}
samwize
  • 25,675
  • 15
  • 141
  • 186
  • There's a caveat. The `sink` is emitted before the value is changed. If you need the value after it is updated, refer to https://samwize.com/2020/06/03/subscribing-to-published/. – samwize Jun 03 '20 at 05:06
6

I don't use combine for this. This it's working for me:

 TextField("write your answer here...",
            text: Binding(
                     get: {
                        return self.query
                       },
                     set: { (newValue) in
                        self.fetch(query: newValue) // any action you need
                                return self.query = newValue
                      }
            )
  )

I have to say it's not my idea, I read it in this blog: SwiftUI binding: A very simple trick

abanet
  • 1,327
  • 17
  • 22
2

If you want to observe value then it should be a State

@State var value: String = ""
Lu_
  • 2,577
  • 16
  • 24
  • I also tried to use didSet for a State var, but it did not work – Sorin Lica Jun 24 '19 at 11:39
  • why are you trying dot set Did set? remove `$value.sink { (val) in print(val) }` and leave just a print and it will work – Lu_ Jun 24 '19 at 11:52
  • 1
    $value contains the whole textfield value, while ```$value.publisher().sink { }``` let you do something at each keystroke. Check my posted answered. – kontiki Jun 24 '19 at 12:53
  • 1
    Not working for me as I think `$value.publisher().sink { }` is deprecated in Beta 6. Do you have any other solution how to observe TextField value – Evana Sep 03 '19 at 03:37
  • 1
    @Evana it's still there just call `value.publisher.sink { }`. – Mark Aug 29 '20 at 10:23
0

You can observe TextField value by using ways,

import SwiftUI
import Combine

struct ContentView: View {
     
    @State private var Text1 = ""
    @State private var Text2 = ""
    @ObservedObject var viewModel = ObserveTextFieldValue()
    
    var body: some View {
        
            //MARK: TextField with Closures
              TextField("Enter text1", text: $Text1){
                  editing in
                  print(editing)
              }onCommit: {
                  print("Committed")
              }
        
            //MARK: .onChange Modifier
              TextField("Enter text2", text: $Text2).onChange(of: Text2){
                  text in
                     print(text)
              }
    
            //MARK: ViewModel & Publisher(Combine)
              TextField("Enter text3", text: $viewModel.value)
    }
}



class ObserveTextFieldValue: ObservableObject {
  @Published var value: String = ""
  private var cancellables = Set<AnyCancellable>()
  init() {
      $value.sink(receiveValue: {
          val in
          print(val)
      }).store(in: &cancellables)
  }
}
Ronak Patel
  • 609
  • 4
  • 16
0

@Published is one of the most useful property wrappers in SwiftUI, allowing us to create observable objects that automatically announce when changes occur that means whenever an object with a property marked @Published is changed, all views using that object will be reloaded to reflect those changes.

import SwiftUI

struct ContentView: View {
    @ObservedObject var textfieldData = TextfieldData()
    var body: some View {
        TextField("Input:", text: $textfieldData.data)
    }
}

class TextfieldData: ObservableObject{
   @Published var data: String = ""
}


struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}
Saurabh Pathak
  • 879
  • 7
  • 10