Search code examples
iosobjective-cmvvmreactive-cocoa

ReactiveCocoa, combine two signals on button enabled


I'm using MVVM architecture and I have two signals:

RACSignal *internetEnabledSignal = RACObserve(self.regWizardVehicleViewModel, internetConnectionEnabled);
RACSignal *executingRegistrationSignal = RACObserve(self.regWizardVehicleViewModel, isExecuting);

I need to combine and bind signals on button enabled property. Button needs to be disabled if there is not any connection OR when some method is executing. I was searching and searching for solution but I don't know how to combine signals with OR. Is there any way to do that? Next question is: I'm using MBProgressHUD. I want to show that HUD while executing my async method in my Model. MBProgressHUD has show and hide methods, can't bind it to a property like button enabled?


Solution

  • If I'm not misunderstanding you, then the functionality should be easily achievable with combineLatest:reduce:, like so:

    RACSignal * enabledSignal = 
    [RACSignal combineLatest:@[internetEnabledSignal, executingRegistrationSignal] 
    reduce:^id(NSNumber * internetEnabled, NSNumber * isExecuting) {
        return @(internetEnabled.boolValue && !isExecuting.boolValue);
    }].distinctUntilChanged;
    
    RAC(self.button, enabled) = enabledSignal;
    

    combineLatest:reduce: won't send any next events until all signals have fired at least once, so please keep that in mind.

    Edit: Please see Michał's answer for a much cooler solution via convenience methods.

    As for your other question, I'm not very familiar with MBProgressHUD but this should do the trick:

    [self.hud rac_liftSelector:@selector(show:) withSignalsFromArray:@[
        [enabledSignal ignore:@NO]
    ]];
    [self.hud rac_liftSelector:@selector(hide:) withSignalsFromArray:@[
        [[enabledSignal ignore:@YES] mapReplace:@YES]
    ]];
    

    This is a bit of a cheat, we're leveraging RAC's eager subscription to these signals to fire side effects, but if you're a pragmatist and not a pedantic idealist like me, you probably don't mind.

    What's happening with this code is that we're asking RAC to perform the given selector after every one of the signals in the array we supplied fires, so it's a lot like combineLatest:. We're using mapReplace: because it'll use that signal as the (BOOL)animated argument (this is another reason why it's a cheat).