How to Bind Multiple Observers to One Controlproperty

How to bind multiple observers to one ControlProperty

Does this accomplish what you want?

let observable = slider.rx.value.shareReplay(1)

observable.map { [unowned self] in self.formatter.string(from: NSNumber(value: $0)) ?? "" }
.bindTo(textFieldAlpha.rx.text)
.disposed(by: disposeBag)

observable.map { Enhance.Global(alpha: $0) }
.bindTo(enhance)
.disposed(by: disposeBag)

Alternative if you do not need the buffer

let observable = slider.rx.value.share()

Here is also a link to a nice cheat sheet for RxSwift https://www.cheatography.com/donghua-li/cheat-sheets/rxswift-operators/

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)

Custom UIControl subclass with RxSwift

If you are subclassing from UIControl, then you are making your own control class and you have to override one or more of beginTracking(_:with:), continueTracking(_:with:), endTracking(_:with:), or cancelTracking(with:) to make the control work the way you want. Then call sendActions(for:) with the correct event. The guts of a UIControl would not have Rx in it.

Taking a queue from UIButton, your control should not select itself, although it can highlight and unhighlight itself (when the user's finger is on it for example.)

Once you have properly created your UIControl, code outside the control can use Rx to observe it with no extra work on your part.

The following works (Updated for Swift 5/RxSwift 5):

class ViewController: UIViewController {

@IBOutlet weak var yesNoButton: SYYesNoButton!
private let bag = DisposeBag()

override func viewDidLoad() {
super.viewDidLoad()

yesNoButton.rx.controlEvent(.touchUpInside)
.scan(false) { v, _ in !v }
.bind(to: yesNoButton.rx.isSelected)
.disposed(by: bag)
}
}

@IBDesignable
class SYYesNoButton: UIControl {

override func layoutSubviews() {
super.layoutSubviews()
backgroundColor = isSelected ? .green : .red
}

override var isSelected: Bool {
didSet {
super.isSelected = isSelected
backgroundColor = isSelected ? .green : .red
}
}
}

How to manually perform action for UITextField editing changed observed with rx?

Simply:

view.textField.insertText("someText")

Bind Winform control property to a property on User Control using INotifyPropertyChanged

Why you don't fire off an event when your IsComplete property gets set? This sounds like a pretty trivial thing to do with either events or delegates in c#?!

If you have more complex sort of requirements you should look into the the observer desgin pattern here and here.

It basically allows you to register listener objects to whatever object that changes its states and then action accordingly.

RxSwift - make one UI element hidden/not hidden according to other element

I believe you can use KVO as described here -
https://github.com/ReactiveX/RxSwift/blob/master/Documentation/GettingStarted.md#kvo

One-way binding

Looks like you are seeking for Binding.ControlUpdateMode property:

Gets or sets when changes to the data source are propagated to the bound control property

myForm.DataBindings.Add(new Binding("Items", ItemsController.Singleton, "Items")
{
DataSourceUpdateMode = DataSourceUpdateMode.OnPropertyChanged,
ControlUpdateMode = ControlUpdateMode.Never
});


Related Topics



Leave a reply



Submit