I'm trying to get to grips with RxCocoa
and have experienced an unusual bug relating to some dynamic UI behaviour I'm trying to implement.
I have a UITextField
that's used for user input. The button which adds the input to a Realm database is bound to an RxSwift
Action. This works absolutely fine.
Initially, I disabled the button until there was text of at least 1 character in length in the UITextField
- the code of this works fine. The bug in my code arose when I then added a subscription to the Action's executionObservables parameter that should clear the UITextField after the button is pressed.
Expected behaviour:
Actual behaviour:
Adding debug() indicates that the binding to the UITextField that disables the button is disposed but I can't figure out why as the UIViewController and its associated view model should still be in scope. Can anyone point me in the right direction?
Code snippet:
func bindViewModel() {
// populate table
viewModel.output.sectionedObservations
.drive(tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
// only allow enable button when there is text in the textfield
observationTextField.rx.text
.debug()
.map { $0!.count > 0 }
.bind(to: addObservationButton.rx.isEnabled)
.disposed(by: disposeBag)
// clear textfield once Action triggered by button press has completed
viewModel.addObservation.executionObservables
.subscribe({ [unowned self] _ in
self.observationTextField.rx.text.onNext("")
})
.disposed(by: disposeBag)
// add Observation to Realm using Action provided by the view model
addObservationButton.rx.tap
.withLatestFrom(observationTextField.rx.text.orEmpty)
.take(1)
.bind(to: viewModel.addObservation.inputs)
.disposed(by: disposeBag)
}
I think there is a little misunderstanding about how ControlProperty
trait behaves. Let's take a look at specific behavior which is Programmatic value changes won't be reported
This Observable observationTextField.rx.text
after subscription will not emit event for both:
self.observationTextField.rx.text.onNext("")
or
self.observationTextField.text = ""
I have 2 suggestion for your code:
1) Do the job manually:
viewModel.addObservation.executionObservables
.subscribe({ [unowned self] _ in
self.observationTextField = ""
self.addObservationButton.isEnabled = false
})
.disposed(by: disposeBag)
2) Add one more Observable and subscription:
//a
viewModel.addObservation.executionObservables
.map { _ in return "" }
.bind(to: observationTextField.rx.text)
.disposed(by: disposeBag)
viewModel.addObservation.executionObservables
.map { _ in return false }
.bind(to: addObservationButton.rx.isEnabled)
.disposed(by: disposeBag)
//b
let executionObservables = viewModel.addObservation
.executionObservables
.share()
executionObservables
.map { _ in return "" }
.bind(to: observationTextField.rx.text)
.disposed(by: disposeBag)
executionObservables
.map { _ in return false }
.bind(to: addObservationButton.rx.isEnabled)
.disposed(by: disposeBag)
Not sure how Action
is implemented, to prevent job done twice maybe you have to share resources.