Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Custom UIControl subclass with RxSwift

I am creating a custom subclass of UIControl (I need to override its draw method) and I want to add RxSwift to bind its isSelected property to my model.

So far so good. This works fine.

My problem is how can I do to change the value isSelected property in response of user touchUpInside event?.

My first try was to use the addTarget method of UIControl, but changing the value of isSelected programmatically is not reported by the ControlProperty (as stated in the doc). But I can figure another way to resolve this.

Any help appreciated.

Source code of the subclass:

class SYYesNoButton: UIControl {

override init(frame: CGRect) {
    super.init(frame: frame)

    // subscribe to touchUpInside event

    addTarget(
        self,
        action: #selector(userDidTouchUpInside),
        for: UIControlEvents.touchUpInside)
}



func userDidTouchUpInside() {

    // change the value of the property
    // this does not work, 
    // the change is not reported to the ControlProperty
    // HOW CAN I CHANGE THIS ??

    self.isSelected = !isSelected
}

}

Extensions to add reactive support:

extension SYYesNoButton {

    var rx_isSelected: ControlProperty<Bool> {

    return UIControl.valuePublic(
        self,
        getter: { (button) -> Bool in
            return button.isSelected
    },
        setter: { (button, value) in
            button.isSelected = value
    })


    }
}



extension UIControl {

    static func valuePublic<T, ControlType: UIControl>(_ control: ControlType, getter:  @escaping (ControlType) -> T, setter: @escaping (ControlType, T) -> ()) -> ControlProperty<T> {



        let values: Observable<T> = Observable.deferred { [weak control] in
            guard let existingSelf = control else {
                return Observable.empty()
            }

            return (existingSelf as UIControl).rx.controlEvent([.allEditingEvents, .valueChanged])
                .flatMap { _ in
                    return control.map { Observable.just(getter($0)) } ?? Observable.empty()
                }
                .startWith(getter(existingSelf))
        }


        return ControlProperty(values: values, valueSink: UIBindingObserver(UIElement: control) { control, value in
            setter(control, value)
        })

    }
}

Thanks for all.

like image 508
t4ncr3d3 Avatar asked May 04 '17 15:05

t4ncr3d3


1 Answers

Once you have an actual UIControl, there's an even nicer way to a "native" RxCocoa extension called a ControlProperty using a helper method in RxCocoa.

For example:

extension Reactive where Base: someControl {
    var someProperty: ControlProperty<Float> {
        return controlProperty(editingEvents: .valueChanged,
                               getter: { $0.value },
                               setter: { $0.value = $1 })
    }
}

This will expose the current value from the getter block whenever the specified UIControlEvent is fired, and will also set the value whenever some stream is bound to it.

It sort of acts like an Observable and Observer type together - you can observe its value, but can also subscribe to it.

like image 70
Shai Mishali Avatar answered Oct 05 '22 22:10

Shai Mishali