1

I have been facing an issue with binding UITextField or button with observables in viewModel.

class VM {
    var emailObservable: Observable<String?> = Observable.just("")
}

I have this observable for email in my viewModel and in controller. When i try to bind my textfield with it, it gives me error

Cannot invoke 'bind' with an argument list of type '(to: Observable)'.

But when i replace the observables with Variable, it works fine.

Can someone please help me with this. I found answers which mainly include passing the observable in the init method of viewModel, but i don't want to pass it in the init method.

This is the link i found for binding but it is through init method.

How to bind rx_tap (UIButton) to ViewModel?

pacification
  • 5,838
  • 4
  • 29
  • 51
Ravi Gupta
  • 97
  • 2
  • 5

5 Answers5

2

Instead of

emailTextfield.rx.text.asObservable().bind(to: viewModel.emailObservable).disposed(by: disposeBag)

use this code

viewModel.emailObservable.bind(to: noteField.rx.text).disposed(by: disposeBag)

Probably, you want to make two way binding, so read more about it here

Vlad Khambir
  • 4,313
  • 1
  • 17
  • 25
1

I think here what you looking for:

final class ViewModel {

    private let bag = DisposeBag()
    let string = BehaviorSubject<String>(value: "")

    init() {
        string.asObservable().subscribe(onNext: { string in
            print(string)
        })
        .disposed(by: bag)
    }

}

final class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    private let bag = DisposeBag()
    private var viewModel: ViewModel!

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel = ViewModel()

        textField.rx.text
            .orEmpty
            .bind(to: viewModel.string)
            .disposed(by: bag)
    }

}

Note, as @MaximVolgin mentioned Variable is deprecated in RxSwift 4, so you can use BehaviorSubject or other that's up to you.


UPD.

Implementation with Observable only.

final class ViewModel {

    private let bag = DisposeBag()

    var string = "" {
        didSet {
            print(string)
        }
    }

    init(stringObservable: Observable<String>) {
        stringObservable.subscribe(onNext: { string in
            self.string = string
        })
        .disposed(by: bag)
    }

}

final class ViewController: UIViewController {

    @IBOutlet weak var textField: UITextField!

    private let bag = DisposeBag()
    private var viewModel: ViewModel!

    override func viewDidLoad() {
        super.viewDidLoad()

        viewModel = ViewModel(stringObservable: textField.rx.text.orEmpty.asObservable())
    }

}

As you can see, your solution can be implemented using Observable, not Variable or any kind of Subject. Also should be mentioned that in most cases this is not the final logic (just bind textField or whatever to some variable). There can be some validation, enable/disable, etc. logic. For this cases RxSwift provide Driver. Also nice example about differences in using Observable and Driver for one project can be found here (by RxSwift).

pacification
  • 5,838
  • 4
  • 29
  • 51
  • Hi @pacification, thanks for the solution but i have read in many blogs to avoid use of Subjects. So i am looking for a solution with Observables. I can use subject and listen it in the viewmodel but i don't want to use it. In the example i used Variable because it is not working with observables. So if you can provide me a solution with observables then that will be great and again thank you for your precious time. – Ravi Gupta Feb 06 '18 at 08:06
  • Hi @pacification. So in all the solutions i only find the instantiation of viewmodel in controller itself so that all the observables can be passed in the init method. As in the comment above i said i don't want to do viewmodel initialisation in the controller. So i want to find a better way to do all the binding. – Ravi Gupta Feb 06 '18 at 11:02
  • 1
    thank you for your precious time and yh i will look into that more. Thanks once again. – Ravi Gupta Feb 06 '18 at 11:21
0

Method .bind(to:) binds to an Observer, not Observable.

Variable (deprecated in RxSwift v4) is a special-purpose Subject.

Subjects are by definition both Observer and Observable.

This is what .bind(to:) does inside -

public func bind<O: ObserverType>(to observer: O) -> Disposable where O.E == E {
    return self.subscribe(observer)
}

UPDATE:

How to avoid passing observables in .init() of VM:

// inside VM:

fileprivate let observableSwitch: BehaviorSubject<Observable<MyValue>>
fileprivate let myValueObservable = observableSwitch.switchLatest()

// instead of passing in init:

public func switch(to observable: Observable<MyValue>) {
  self.observableSwitch.onNext(observable)
}
Maxim Volgin
  • 3,957
  • 1
  • 23
  • 38
  • Hi @maxim-volgin thanks for your answer. So if i can't bind the textfields that way, can you tell me a better work around for it. In the initialiser method the view model is instantiated inside the controller and not passed as dependency. I am looking for a better way for binding. – Ravi Gupta Feb 05 '18 at 19:33
  • You can use *BehaviorRelay* instead of *Variable*. – Maxim Volgin Feb 06 '18 at 00:17
  • Ok will look into that. Can you tell me the best approach for doing such kind of bindings, that will be really helpful. Thanks again for your suggestion. – Ravi Gupta Feb 06 '18 at 03:41
  • Can you provide more context? Bindings are just syntactic sugar, you don't have to use them if they are not available - plain subscriptions are perfectly fine. Personally, I only use bindings for RxDataSources. Also I don't use MVVM, only unidirectional dataflow - https://github.com/maxvol/RaspSwift – Maxim Volgin Feb 06 '18 at 06:23
  • Hi @maxim-volgin. So i have a controller with textfields in it and for every changes in textfields i want to show validation state of each textfields. For example, if textfield have some valid data then show some image in rightView of textfield else show other image for invalid state. So for this i need to observe changes in each textfield in viewmodel or presenter. So for this i want to know how to bind textfields with viewmodel or presenter. – Ravi Gupta Feb 06 '18 at 11:09
  • i would use *.combineLatest()* for validation of the form fields and subscribe button state to it. – Maxim Volgin Feb 06 '18 at 15:09
  • Hi @maxim-volgin. I am using combine latest and doing everything. So i just need to know how to bind the observables in viewmodel or presenter from viewcontroller. All the solutions include passing observables in init method of viemodel but i don't want to do it, there should another way for binding. – Ravi Gupta Feb 07 '18 at 08:13
  • hi @ravi-gupta, if this is your only concern then *.switchLatest()* operator is your friend. I will update my answer shortly, – Maxim Volgin Feb 07 '18 at 09:33
  • Hi @maxim-volgin. Thanks for the answer. I have read in many posts that not to use Subjects in code. So instead of making a subject of observables i can make different observables and then assign them in the viewcontroller. Will that work? – Ravi Gupta Feb 07 '18 at 11:09
  • I have no clue why people avoid subjects. Do they provide any valid reasons? I work with Rx for many years and never heard any. – Maxim Volgin Feb 07 '18 at 11:11
  • I am thinking of something like this. Like instead of passing observables in init i assign observables in viewmodel from viewcontroller individually. For example, i have a text observable in viewmodel. VM { var textObservable: Observable? // more observables of textfields } and i assign it in viewcontroller viewmodel.textObservable = myTextField.rx.text.asObservable() Should i do something like this? – Ravi Gupta Feb 07 '18 at 11:13
  • Seems like an unjustified overcomplication to me. Please share these posts saying not to use Subjects, you got me curious. – Maxim Volgin Feb 07 '18 at 11:18
0

Take a subject of variable type in ViewModel class:

class ViewModel{

//MARK: - local Variables
var emailText = Variable<String?>("")
}       

Now create object of viewmodel class in viewController class and bind this emailtext variable to textfield in viewcontroller.Whenever textfield text will change then it emailText of viewmodel gets value.

txtfield.rx.text
        .bindTo(viewModel.emailText).addDisposableTo(disposeBag)
Arnav
  • 668
  • 6
  • 14
  • Hi @arnav. I have found in many posts not to use subjects. So can you tell me a way to bind textfields with observables instead and i don't want the binding through init method of viewmodel (in link i shared above). – Ravi Gupta Feb 10 '18 at 07:25
0

Try this,

override func viewDidLoad() {
        super.viewDidLoad()
        _ = userNameTextField.rx.text.map { $0 ?? "" }.bind(to: viewModel.userName)
    }

in viewModel class,

class ViewModel{
   var userName: Variable<String> = Variable("")
}
Soumen
  • 2,070
  • 21
  • 25