Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

RxSwift how to conditionally control when a button emits taps?

I want to prevent my UIButtons from emitting RX tap events until some control stream indicates that it is ready. I want buttons to be disabled again if the control stream reverts back to not ready. Control sequence is a ReplaySubject and always has value

I've tried skipUntil, but that's a one shot operation - once the control sequence emits, it cannot go back to disabled state - all button presses will go through, ignoring the control sequence

How can I restrict a UIButton from sending tap events unless some other stream has a certain value?

let enableButtons = configStatusInputSequence
.filter { (configured, ready) -> Bool in
    return configured && ready
}

for button in controlButtons{
    button.rx.tap
        .skipUntil(enableButtons)
        .bind(to: commandOutputSequence)
        .disposed(by: bag)
}
like image 809
Alex Stone Avatar asked Sep 09 '18 11:09

Alex Stone


1 Answers

Here's something I wrote a while back and reproduced below. If you look at my gist (https://gist.github.com/danielt1263/1a70c4f7b8960d06bd7f1bfa81802cc3) you will see that I originally wrote it as a custom operator. Later I learned that the combination of built in operators below do the same job.

If nothing else, looking back at the older revisions in the gist will give you the sense of how to write your own operators.

extension ObservableType {

    /**
     Filters the source observable sequence using a trigger observable sequence producing Bool values.
     Elements only go through the filter when the trigger has not completed and its last element was true. If either source or trigger error's, then the source errors.
     - parameter trigger: Triggering event sequence.
     - returns: Filtered observable sequence.
     */
    func filter(if trigger: Observable<Bool>) -> Observable<E> {
        return withLatestFrom(trigger) { (myValue, triggerValue) -> (Element, Bool) in
                return (myValue, triggerValue)
            }
            .filter { (myValue, triggerValue) -> Bool in
                return triggerValue == true
            }
            .map { (myValue, triggerValue) -> Element in
                return myValue
            }
    }
}

If you want to change what the button does depending on the value of your control observable, setup two filters. Normally the filter only passes through taps when the enableButtons emits true. Use a map to reverse it in the second case and direct button taps down another path:

button.rx.tap.filter(if: enableButtons)
    .subscribe(onNext: { /* do one thing when enableButtons emits true */ }
    .disposed(by: bag)

button.rx.tap.filter(if: enableButtons.map { !$0 })
    .subscribe(onNext: { /* do other thing when enable buttons emits false*/ }
    .disposed(by: bag)
like image 130
Daniel T. Avatar answered Sep 28 '22 00:09

Daniel T.