0
votes

Here's the problem up front: I have a UIAlertController that has a textfield. I want to save the content of that textfield as an NSString when the user touches a "Confirm" button in the alert. When the Confirm action block is executed, however, the alert is nil (presumably already dismissed and deallocated at that point), and thus so is its textfield, meaning I cannot save the textfield's text.

I am using a series of UIAlertControllers to allow a user to create a passcode for my app, such that any time the app comes to the foreground, the user is prompted for the code before the app can be used.

I created a category of UIAlertController with several convenience methods that return preconfigured alerts that I need to use. Here's one of them:

+ (UIAlertController*)passcodeCreationAlertWithConfirmBehavior:(void(^)())confirmBlock andCancelBehavior:(void(^)())cancelBlock {
UIAlertController *passcodeCreationAlert = [UIAlertController alertControllerWithTitle:@"Enter a passcode"
                                                                               message:nil
                                                                        preferredStyle:UIAlertControllerStyleAlert];

[passcodeCreationAlert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
    textField.keyboardType = UIKeyboardTypeNumberPad;
}];

UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
                                                       style:UIAlertActionStyleCancel
                                                     handler:^(UIAlertAction * action) {
                                                         if (cancelBlock) {
                                                             cancelBlock();
                                                         }
                                                     }];

UIAlertAction* confirmAction = [UIAlertAction actionWithTitle:@"Confirm"
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction * action) {
                                                          if (confirmBlock) {
                                                              confirmBlock();
                                                          }
                                                      }];

[passcodeCreationAlert addAction:cancelAction];
[passcodeCreationAlert addAction:confirmAction];
passcodeCreationAlert.preferredAction = confirmAction;
confirmAction.enabled = NO;

return passcodeCreationAlert;
}

This method returns a UIAlertController that allows the user to enter their desired passcode into a textfield. When I call this method in my view controller, I pass blocks as parameters which are used as the UIAlertAction handlers:

- (void)presentCreatePasscodeAlert {
UIAlertController *alert = [UIAlertController passcodeCreationAlertWithConfirmBehavior:^{
    firstPasscode = alert.textFields[0].text;
    [self presentConfirmPasscodeAlert];
} andCancelBehavior:^{
    [self presentEnablePasscodeAlert];
}];


alert.textFields[0].delegate = self;

[self presentViewController:alert animated:YES completion:nil];
}

To reiterate the problem now that there is more context: When the action block is entered at the line:

firstPasscode = alert.textFields[0].text;

the alert is nil, and so is its textfield, meaning I cannot save the textfield's text.

In a separate project, however, I tried getting the same functionality without using the category and custom convenience methods, and that works as desired:

- (void) createPassword {
UIAlertController *createPasswordAlert = [UIAlertController alertControllerWithTitle:@"Enter a password"
                                                                             message:nil
                                                                      preferredStyle:UIAlertControllerStyleAlert];

__weak ViewController *weakSelf = self;

[createPasswordAlert addTextFieldWithConfigurationHandler:^(UITextField * _Nonnull textField) {
    textField.delegate = weakSelf;
    textField.keyboardType = UIKeyboardTypeNumberPad;
}];


UIAlertAction* confirmAction = [UIAlertAction actionWithTitle:@"Confirm"
                                                        style:UIAlertActionStyleDefault
                                                      handler:^(UIAlertAction * action) {
                                                          self.password = createPasswordAlert.textFields[0].text;
                                                          [self confirmPassword];
                                                      }];

UIAlertAction* cancelAction = [UIAlertAction actionWithTitle:@"Cancel"
                                                       style:UIAlertActionStyleDefault
                                                     handler:^(UIAlertAction * action) {
                                                         [self promptPasswordCreation];
                                                     }];

[createPasswordAlert addAction:confirmAction];
[createPasswordAlert addAction:cancelAction];

confirmAction.enabled = NO;

[self presentViewController:createPasswordAlert animated:YES completion:nil];
}

Sure enough, in the above code the alert exists when the Confirm block is entered, and I can save the text just fine.

Have I done something screwy by passing blocks as parameters to my convenience methods?

1

1 Answers

0
votes

One of your problems is that when you create the block and send it into the initializer you are referencing a nil object inside the block. Then the block is saved with that nil reference and passed as a block to your handler.

UIAlertController *alert = [UIAlertController passcodeCreationAlertWithConfirmBehavior:^{
    firstPasscode = alert.textFields[0].text;
    [self presentConfirmPasscodeAlert];
} andCancelBehavior:^{
    [self presentEnablePasscodeAlert];
}];

In there alert is not initialized yet when you create that block and send it to be saved. An option to fix that would be to use a standard initializer and then categorize a method to add the block functions that you want. Another option might be to try to add the __block modifier to the alert object, although I don't think that will help.