Rxswift: Two Way Binding

Two way binding in RxSwift

Thanks for raising the question, I spent some time digging around the ControlProperty implementation (note I've added a .debug() call to trace the values generated for control property).

public struct ControlProperty<PropertyType> : ControlPropertyType {
public typealias E = PropertyType

let _values: Observable<PropertyType>
let _valueSink: AnyObserver<PropertyType>

public init<V: ObservableType, S: ObserverType where E == V.E, E == S.E>(values: V, valueSink: S) {
_values = values.debug("Control property values").subscribeOn(ConcurrentMainScheduler.instance)
_valueSink = valueSink.asObserver()
}

public func on(event: Event<E>) {
switch event {
case .Error(let error):
bindingErrorToInterface(error)
case .Next:
_valueSink.on(event)
case .Completed:
_valueSink.on(event)
}
}
}

My test setup was as following, I've removed all views positioning here to make it shorter:

import UIKit
import RxSwift
import RxCocoa
class ViewController: UIViewController {
let variable = Variable<Bool>(false);
let bag = DisposeBag();

override func loadView() {
super.loadView()

let aSwitch = UISwitch();
view.addSubview(aSwitch)

(aSwitch.rx_value <-> variable).addDisposableTo(bag);

let button = UIButton();
button.rx_tap.subscribeNext { [weak self] in
self?.variable.value = true;
}.addDisposableTo(bag)
view.addSubview(button);
}
}

infix operator <-> {
}

func <-> <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable{
let bindToUIDisposable = variable.asObservable().debug("Variable values in bind")
.bindTo(property)

let bindToVariable = property
.debug("Property values in bind")
.subscribe(onNext: { n in
variable.value = n
}, onCompleted: {
bindToUIDisposable.dispose()
})

return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable)
}

Now to the results. First we try tapping the button, which should set the variable to true. This triggers on(event: Event<E>) on ControlProperty and sets the switch value to true.

2016-05-28 12:24:33.229: Variable values in bind -> Event Next(true)

// value flow
value assigned to Variable ->
Variable emits event ->
ControlProperty receives event ->
value assigned to underlying control property (e.g. `on` for `UISwitch`)

Next lets trigger the switch itself. So as we can see, the control generated an event as a result of UIControlEventValueChanged which was passed through _values on ControlProperty, and then its value got assigned to Variable value as in example above. But there's no loop, since update to the Variable value doesn't trigger a control event on the switch.

2016-05-28 12:29:01.957: Control property values -> Event Next(false)
2016-05-28 12:29:01.957: Property values in bind -> Event Next(false)
2016-05-28 12:29:01.958: Variable values in bind -> Event Next(false)

// value flow
trigger the state of control (e.g. `UISwitch`) ->
ControlProperty emits event ->
value assigned to Variable ->
Variable emits event ->
ControlProperty receives event ->
value assigned to underlying control property (e.g. `on` for `UISwitch`)

So a simple explanation would be:

  • a value from a control is emitted once some kind of UIControlEvent is triggered
  • when a value is assigned directly to the control property, the control doesn't trigger a change event so there's no loop.

Hope it helps, sorry for a bit messy explanation - I've found it out by experiment)

Two ways binding textfiled in RxSwift in different type

I missing pass bag when I call the function. Exactly, it should be:

let bag = DisposeBag()
textField1.bind(with: viewModel.fullName, bag: bag) //working well
textField2.bind(with: viewModel.phone, bag: bag)

Also I need to remove default value for avoiding forgetting in the future

func bind(with property: BehaviorRelay<Int?>, bag: DisposeBag) {
property.map { intValue in
return Int.toString(intValue) //convert from Int to String
}.bind(to: self.rx.text).disposed(by: bag)
self.rx.text.orEmpty.map { text in
return String.toInt(text) ?? 0 //convert from String to Int
}.bind(to: property).disposed(by: bag)
}

RxSwift Two way binding with a BehaviorRelayAny?

(The two-way binding doesn't cause an infinite loop in this case because UI elements don't emit values when modified programmatically, they only do it when the user changes the value. If the user flips the switch, it gets pushed to the Subject [BehaviorRelay] which then pushes the value back to the switch, but it ends there.)

You would have to cast your Element to an Any? with map and map the Any? back into the proper type when going from the BehaviorRelay to the UI element. It's very clunky and unsafe to do that.

Better would be to make your BehaviorRelay's element the correct type. You're going to have to know the type in order to do anything useful with it anyway.

Swift 5: How to get the value of a BehaviorRelay and binding with RxSwift and MVVM

Here is the most obvious simplification:

class AmountViewModel {
let amount: Observable<Int>

init(accountService: AccountManagerProtocol = AccountManager()) {
amount = accountService.getAccount()
.map { $0.amount ?? 0 }
}
}

private extension AmountViewController {
private func bindViewModel() {
viewModel.amount
.compactMap { $0.currencyToString().map { "BALANCE: \($0)"} }
.observe(on: MainScheduler.instance)
.bind(to: inputAmountView.amountLabel.rx.text)
.disposed(by: disposeBag)
}
}

But I think I would move the code in the compactMap closure into the view model...



Related Topics



Leave a reply



Submit