Search code examples
iosobjective-creactive-cocoa

Combining signals but just subscribe when first one changes


I want to combine the rac_signalForControlEvent on a UIButton with some combined textFields signals like so:

    [[[[self.loginButton rac_signalForControlEvents:UIControlEventTouchUpInside]
    combineLatestWith:textFieldsCombinedSignal]
    filter:^BOOL(RACTuple *signals) {
      return ((UIButton *)[signals first]).highlighted;
    }]  subscribeNext:^(RACTuple *signals) {
      if ([signals.second boolValue])
      {
          [self doLogin];
      }
      else
      {
          [self error];
      }
    }];

But this way I have to filter for the button highlighted state otherwise the subscribeNext: block is getting fired every time some of the textfields change (textFieldsCombinedSignal).

I would love to achieve this without having to filter for the highlighted button's state (I'm using ReactiveCocoa to minimize state after all, and I don't feel like this is the proper way to do what I'm trying to do).


Solution

  • If you want a sequence of button tap and then the latest from your combined text field signal, it can be as simple as using -flattenMap::

    [[[self.loginButton
        rac_signalForControlEvents:UIControlEventTouchUpInside]
        flattenMap:^(id _) {
            return [textFieldsCombinedSignal take:1];
        }]
        subscribeNext:^…];
    

    However, from what you've described, this seems like a classic case for RACCommand. Both this code, and the original code, allow for the user to double tap the login button and trigger concurrent logins.

    Before showing RACCommand, I'll make a couple of assumptions about your code. From the name alone, textFieldsCombinedSignal it could be a signal that sends a tuple of strings, but in your use it looks like it's actually a validation signal that sends YES/NO. I'll assume the latter and rename it to loginIsValid. I'll also assume that -doLogin is synchronous for the purpose of this example.

    Now to RACCommand:

    self.loginButton.rac_command = [[RACCommand alloc] initWithEnabledSignal:loginIsValid signalBlock:^(id _) {
        return [RACSignal defer:^{
            [self doLogin];
            return [RACSignal empty];
        }];
    }];
    

    What this will do is enable/disable the login button based on the latest value sent on loginIsValid. When enabled, a tap on the button will cause -doLogin to be called, and the button will become disabled for the duration of the login process, preventing concurrent logins.