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:
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.
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!
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With