Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Combining multiple signal not working using Reactive Cocoa

I am following this tutorial: http://www.raywenderlich.com/62699/reactivecocoa-tutorial-pt1. It is working for username and password field.

I am trying to create sign up form which contains three fields: email, password, confirmPassword.

I have validated email address (working perfectly) I have validated password like this:

password should be at least 6 characters and both password and confirmPassword must be same.

Here is my code:

RACSignal *validEmailSignal =
[emailTextField.rac_textSignal
 map:^id(NSString *text) {
     return @([text isValidEmailAddress]);
 }];

RACSignal *validPasswordSignal =
[passwordTextField.rac_textSignal
 map:^id(NSString *text) {
     return @([text isEqualToString:self.confirmPasswordTextField.text] && text.length >5);
 }];

RACSignal *validConfirmPasswordSignal =
[confirmPasswordTextField.rac_textSignal
 map:^id(NSString *text) {
     return @([text isEqualToString:self.passwordTextField.text] && text.length >5 );
 }];

[[validEmailSignal
  map:^id(NSNumber *emailValid) {
      return [emailValid boolValue] ? [UIColor greenColor] : [UIColor redColor];
  }]
 subscribeNext:^(UIColor *color) {
     emailTextField.layer.borderColor=[color CGColor];
     emailTextField.layer.borderWidth= 1.0f;

 }];

[[validPasswordSignal
  map:^id(NSNumber *passwordValid) {
      return [passwordValid boolValue] ? [UIColor greenColor] : [UIColor redColor];
  }]
 subscribeNext:^(UIColor *color) {
     passwordTextField.layer.borderColor=[color CGColor];
     passwordTextField.layer.borderWidth= 1.0f;
     confirmPasswordTextField.layer.borderColor=[color CGColor];
     confirmPasswordTextField.layer.borderWidth= 1.0f;
 }];

[[validConfirmPasswordSignal
  map:^id(NSNumber *passwordValid) {
      return [passwordValid boolValue] ? [UIColor greenColor] : [UIColor redColor];
  }]
 subscribeNext:^(UIColor *color) {
     passwordTextField.layer.borderColor=[color CGColor];
     passwordTextField.layer.borderWidth= 1.0f;
     confirmPasswordTextField.layer.borderColor=[color CGColor];
     confirmPasswordTextField.layer.borderWidth= 1.0f;
 }];


RACSignal *signUpActiveSignal =
[RACSignal combineLatest:@[validEmailSignal, validPasswordSignal, validConfirmPasswordSignal]
                  reduce:^id(NSNumber *emailValid, NSNumber *passwordValid, NSNumber *confirmPasswordValid) {
                      return @([emailValid boolValue] && [passwordValid boolValue] && [confirmPasswordValid boolValue]);
                  }];


[signUpActiveSignal subscribeNext:^(NSNumber *signupActive) {

    NSLog(@"sign up button enabled : %hhd", [signupActive boolValue]);
    _signUpButton.enabled = [signupActive boolValue];
}];

Here, if I take only emailValid's value, it is sending 1 when valid. But if I include all three values, it always returns 0 even if all are valid.

Here are snap shots of the simulator:

enter image description hereenter image description here

I just wanted to enable "Sign up" button when all three fields are valid. I also want disable "Go" button on the keyboard till validation success.

like image 450
regeint Avatar asked Apr 15 '14 09:04

regeint


1 Answers

Okay, two things:

You can use the and helper of RACSignal to make that signal reduction a little cleaner:

RACSignal *signUpActiveSignal = [[RACSignal combineLatest:@[validEmailSignal,
                                                            validPasswordSignal,
                                                            validConfirmPasswordSignal]] and];

You can also use the RAC macro to bind the enabled property (the only reason that the example code is using subscribeNext instead of RAC is that CGColorRef isn't of type id):

RAC(self.signUpButton, enabled) = signUpActiveSignal;

Now onto the reason this isn't working. Let's look at the calculations for the password fields:

RACSignal *validPasswordSignal = [passwordTextField.rac_textSignal map:^id(NSString *text) {
    return @([text isEqualToString:self.confirmPasswordTextField.text] && text.length > 5);
}];

RACSignal *validConfirmPasswordSignal = [confirmPasswordTextField.rac_textSignal map:^id(NSString *text) {
    return @([text isEqualToString:self.passwordTextField.text] && text.length > 5);
}];

Every time passwordTextField's text changes, it compares it to confirmPasswordTextField. But it only checks that they're the same when passwordTextField changes, not when confirmPasswordTextField changes. So let's imagine the following scenario, ignoring (for now) the length requirement:

------------------- initial state -------------------

           -------------
password: |             |   passwordValid: YES
           -------------

           -------------
 confirm: |             |   confirmValid: YES
           -------------

-------------- user types in a password --------------

           -------------
password: | asdf        |   passwordValid: NO
           -------------

           -------------
 confirm: |             |   confirmValid: YES (this will only recalculate when the confirm field changes, not when the password field changes)
           -------------


------- user types in the password confirmation -------

           -------------
password: | asdf        |   passwordValid: NO (this didn't recalculate because the contents of the password field didn't change)
           -------------

           -------------
 confirm: | asdf        |   confirmValid: YES
           -------------

Aha! So we need to recalculate when either of them changes. And, really, that should be a separate signal from the lengths:

RACSignal *passwordsMatch = [RACSignal combineLatest:@[passwordTextField.rac_textSignal,
                                                       confirmPasswordTextField.rac_textSignal]
                                              reduce:(NSString *password, NSString *confirm) {
                                                  return @([password isEqualToString:confirm]);
                                              }];

RACSignal *isPasswordLongEnough = [passwordTextField.rac_textSignal map:^(NSString *text) {
    return @(text.length >= 6);
}];

Then we can put it all together:

RACSignal *signUpActiveSignal = [[RACSignal combineLatest:@[validEmailSignal,
                                                            isPasswordLongEnough,
                                                            passwordsMatch]] and];

Which is great, because that's sort of how we think of it: "You can sign in when the email address is valid, the password you entered is long enough, and the password confirm field matches the password." Declarative!

like image 125
Ian Henry Avatar answered Sep 18 '22 22:09

Ian Henry