Custom UIControl subclass with RxSwift

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.


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
        }
    }
}