[英]Two way binding in RxSwift
I read the two way binding operator in sample code of RxSwift.我在 RxSwift 的示例代码中阅读了双向绑定运算符。
func <-> <T>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable {
let bindToUIDisposable = variable.asObservable()
.bindTo(property)
let bindToVariable = property
.subscribe(onNext: { n in
variable.value = n
}, onCompleted: {
bindToUIDisposable.dispose()
})
return StableCompositeDisposable.create(bindToUIDisposable, bindToVariable)
}
When property
changed, it will notify variable, and set the variable's value, while the variable's value is set, it will notify the property.当
property
改变时,它会通知变量,并设置变量的值,当变量的值被设置时,它会通知属性。 I think it will lead to endless loop...我认为这会导致无限循环......
I believe you can just use bindTo
🙂.我相信你可以使用
bindTo
🙂。 Here are implementations for ControlProperty <-> Variable
and Variable <-> Variable
:以下是
ControlProperty <-> Variable
和Variable <-> Variable
:
infix operator <-> { precedence 130 associativity left }
func <-><T: Comparable>(property: ControlProperty<T>, variable: Variable<T>) -> Disposable {
let variableToProperty = variable.asObservable()
.distinctUntilChanged()
.bindTo(property)
let propertyToVariable = property
.distinctUntilChanged()
.bindTo(variable)
return StableCompositeDisposable.create(variableToProperty, propertyToVariable)
}
func <-><T: Comparable>(left: Variable<T>, right: Variable<T>) -> Disposable {
let leftToRight = left.asObservable()
.distinctUntilChanged()
.bindTo(right)
let rightToLeft = right.asObservable()
.distinctUntilChanged()
.bindTo(left)
return StableCompositeDisposable.create(leftToRight, rightToLeft)
}
Examples of ControlProperty <-> Variable
(such as UITextField
and UITextView
) are in the RxSwiftPlayer project ControlProperty <-> Variable
(例如UITextField
和UITextView
)的示例在RxSwiftPlayer 项目中
// Example of Variable <-> Variable
let disposeBag = DisposeBag()
let var1 = Variable(1)
let var2 = Variable(2)
(var1 <-> var2).addDisposableTo(disposeBag)
var1.value = 10
print(var2.value) // 10
var2.value = 20
print(var1.value) // 20
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).感谢您提出这个问题,我花了一些时间研究
ControlProperty
实现(注意我添加了一个.debug()
调用来跟踪为控件属性生成的值)。
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
.首先我们尝试点击按钮,这应该将变量设置为
true
。 This triggers on(event: Event<E>)
on ControlProperty and sets the switch value to true
.这会
on(event: Event<E>)
ControlProperty on(event: Event<E>)
触发on(event: Event<E>)
并将开关值设置为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.正如我们所看到的,控件生成了一个事件作为
UIControlEventValueChanged
的结果,该事件通过 ControlProperty 上的_values
传递,然后它的值被分配给Variable
值,如上例所示。 But there's no loop, since update to the Variable
value doesn't trigger a control event on the switch.但是没有循环,因为对
Variable
值的更新不会触发开关上的控制事件。
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:所以一个简单的解释是:
UIControlEvent
is triggeredUIControlEvent
被触发,就会发出来自控件的值 Hope it helps, sorry for a bit messy explanation - I've found it out by experiment)希望它有所帮助,抱歉解释有点乱——我是通过实验发现的)
You type anything it will be clear after 5 seconds.您输入任何内容,5 秒后就会清楚。 This was taken from above answer
这是从上面的答案中获取的
import UIKit
import RxSwift
import RxCocoa
class UserViewModel {
let username = BehaviorSubject<String?>(value: "")
}
class ViewController: UIViewController {
@IBOutlet weak var email: UITextField!
var userViewModel = UserViewModel()
let bag = DisposeBag()
override func viewDidLoad() {
super.viewDidLoad()
userViewModel.username.asObservable().subscribe { print($0) }.disposed(by: bag)
(email.rx.text <-> userViewModel.username).disposed(by: bag)
// clear value of the username.
DispatchQueue.main.asyncAfter(deadline: .now()+5) {
self.userViewModel.username.onNext(nil)
}
}
}
infix operator <->
@discardableResult func <-><T>(property: ControlProperty<T>, variable: BehaviorSubject<T>) -> Disposable {
let variableToProperty = variable.asObservable()
.bind(to: property)
let propertyToVariable = property
.subscribe(
onNext: { variable.onNext($0) },
onCompleted: { variableToProperty.dispose() }
)
return Disposables.create(variableToProperty, propertyToVariable)
}
There is no obstacle to bind BehaviourRelay
back to control property.将
BehaviourRelay
绑定回控制属性没有任何障碍。 You just need to filter events with the same value (to prevent infinite loop).您只需要过滤具有相同值的事件(以防止无限循环)。
For example, in my case, I need to bind email to text field.例如,就我而言,我需要将电子邮件绑定到文本字段。 But I want to remove whitespaces during email input.
但我想在电子邮件输入期间删除空格。 Here is an example how I achieved it:
这是我如何实现它的示例:
emailTextField.rx.text
.map { $0?.trimmingCharacters(in: CharacterSet.whitespaces) } // remove whitespaces from input
.bind(to: viewModel.email)
.disposed(by: disposeBag)
// Just filter all events with no actual value change to prevent infinite loop
viewModel.email
.filter { $0 != self.emailTextField.text } // if it removed whitespaces in mapping, values will not match and text in text field will be updated
.bind(to: emailTextField.rx.text)
.disposed(by: disposeBag)
The source code in UITextField+Rx.swift
: UITextField+Rx.swift
的源代码:
/// Reactive wrapper for `text` property.
public var value: ControlProperty<String?> {
return base.rx.controlPropertyWithDefaultEvents(
getter: { textField in
textField.text
},
setter: { textField, value in
// This check is important because setting text value always clears control state
// including marked text selection which is imporant for proper input
// when IME input method is used.
if textField.text != value {
textField.text = value
}
}
)
}
The magic is in the setter:魔法在二传手:
if textField.text != value {
textField.text = value
}
So a ControlProperty is two way binding to a Variable,所以 ControlProperty 是绑定到变量的两种方式,
The ControlProperty will not always change, because the if judgement in setter method. ControlProperty 不会一直变化,因为 setter 方法中的 if 判断。
I checked in RxSwift 5.0.1我签入了 RxSwift 5.0.1
@dengApro's answer is very close. @dengApro 的回答非常接近。
The source code in UITextField+Rx.swift
: UITextField+Rx.swift
的源代码:
/// Reactive wrapper for `text` property.
public var value: ControlProperty<String?> {
return base.rx.controlPropertyWithDefaultEvents(
getter: { textField in
textField.text
},
setter: { textField, value in
// This check is important because setting text value always clears control state
// including marked text selection which is imporant for proper input
// when IME input method is used.
if textField.text != value {
textField.text = value
}
}
)
}
Assigning textField
a value could not be subscribed, because of controlPropertyWithDefaultEvents
由于
controlPropertyWithDefaultEvents
,无法订阅为textField
分配值
The source code in UIControl+Rx.swift
: UIControl+Rx.swift
的源代码:
/// This is a separate method to better communicate to public consumers that
/// an `editingEvent` needs to fire for control property to be updated.
internal func controlPropertyWithDefaultEvents<T>(
editingEvents: UIControl.Event = [.allEditingEvents, .valueChanged],
getter: @escaping (Base) -> T,
setter: @escaping (Base, T) -> Void
) -> ControlProperty<T> {
return controlProperty(
editingEvents: editingEvents,
getter: getter,
setter: setter
)
}
So just the two events UIControl.Event = [.allEditingEvents, .valueChanged]
can be observable,所以只有
UIControl.Event = [.allEditingEvents, .valueChanged]
两个事件可以被观察到,
Variable changed, Variable bind to ControlProperty, ControlProperty changed not because of [.allEditingEvents, .valueChanged]
, then done.变量改变,变量绑定到 ControlProperty,ControlProperty 改变不是因为
[.allEditingEvents, .valueChanged]
,然后完成。
ControlProperty changed, ControlProperty bind to Variable, Variable changed and bind to ControlProperty,ControlProperty seted not because of [.allEditingEvents, .valueChanged]
, then done. ControlProperty 更改,ControlProperty 绑定到 Variable,Variable 更改并绑定到 ControlProperty,ControlProperty 设置不是因为
[.allEditingEvents, .valueChanged]
,然后完成。
In the source code of controlProperty
, will establish the UIControl target - action.在
controlProperty
的源代码中,将建立 UIControl 目标 - 动作。
[.allEditingEvents, .valueChanged]
contains of editingDidBegin, editingChanged, editingDidEnd, editingDidEndOnExit, valueChanged, [.allEditingEvents, .valueChanged]
包含editingDidBegin、editingChanged、editingDidEnd、editingDidEndOnExit、valueChanged、
So assigning to textField.text
directly will trigger no event.所以直接赋值给
textField.text
不会触发任何事件。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.