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
Create Navbar Programmatically with Button and Title Swift
Masked Uivisualeffectview Does Not Work on iOS 10
Error When Decoding Certain Base64 Strings, But Not Others
App Crash on Device But Works on Simulator iOS
Audiokit - How to Get Real Time Floatchanneldata from Microphone
Simplest Way to Implement a "Read More" Button to Expand a Uitextview in iOS Swift 2
Swift Tableview Cell Radio Button Implementation
How to Add File Picker to the App on iOS 14+ and Lower
How to Manage and Free Memory Through Viewcontrollers
Import Xctest into a Dynamic Framework
Unusernotificationcenter.Current().Getdeliverednotifications Only Ever Returns an Empty Array
How to Add Custom Data to Marker (Google Maps API Swift)
Apple Push Notifications in Tvos
Cannot Set Color of Button's Label Inside Menu in Swiftui
In iOS Avplayer, Addperiodictimeobserverforinterval Seems to Be Missing