UIAlertController handle dismiss upon click outside (IPad)
Asked Answered
A

3

28

Previous to iOS8 we used the UIActionSheet for showing alert and now we need to use the UIAlertController.

When we used the UIActionSheet we could easily handle situations where the user clicked outside the pop up (which means he want to cancel the operation) by comparing the clickedButtonAtIndex to the cancelButtonIndex - if the user indeed pressed outside the popup we got the cancel button index in this function.

How can we handle these situations with the new UIAlertController? I tried to use the "completion" block but it doesn't have any context. Is there an easy way to handle this? (other than "saving" the actions states in some general variable).

Arboreous answered 23/8, 2014 at 21:54 Comment(0)
S
45

You can add an action with style:UIAlertActionStyleCancel and the handler for this action is called when the user taps outside the popup.

if ([UIAlertController class]) {
    UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert Title" message:@"A Message" preferredStyle:UIAlertControllerStyleActionSheet];

    [alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
        NSLog(@"User clicked button called %@ or tapped elsewhere",action.title);
    }]];

    [alertController addAction:[UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:^(UIAlertAction *action) {
        NSLog(@"User clicked button called %@",action.title);
    }]];

    [alertController addAction:[UIAlertAction actionWithTitle:@"Other" style:UIAlertActionStyleDestructive handler:^(UIAlertAction *action) {
        NSLog(@"User clicked button called %@",action.title);
    }]];

    UIControl *aControl = (UIControl *) sender;
    CGRect frameInView = [aControl convertRect:aControl.bounds toView:self.view];
    alertController.popoverPresentationController.sourceRect = frameInView;
    alertController.popoverPresentationController.sourceView = self.view;
    alertController.popoverPresentationController.permittedArrowDirections = UIPopoverArrowDirectionAny;
    [self presentViewController:alertController animated:YES completion:nil];
}
Sorgo answered 24/8, 2014 at 7:2 Comment(7)
Perhaps I'm missing something (I'm very new to iOS). But in my experience, using UIAlertControllerStyleActionSheet with UIAlertActionStyleCancel seems not to add the Cancel action. Changing the style to UIAlertControllerStyleAlert always shows the cancel action.Finn
Note that the UIAlertController will remove the cancel button when using a popover, even if you add it as an action. A user cancels a popover by touching outside of the popover so that it is not required.Blouse
Note: Having a UIAlertActionStyleCancel styled action only dismisses the AlertController if you are using the UIAlertControllerStyleActionSheet AlertController style. It does not work with the UIAlertControllerStyleAlert.Spirant
@JDG Yeah but the alert style can't be canceled with an outside tap anyway so it doesn't matter.Relict
this is ok for when i give it for button action and i now use this for uitextfield so how i should call this controller, i am also tried to call in the method of "- (BOOL)textFieldShouldBeginEditing:(UITextField *)textField { }" by this the alert view open repeated again and again please help me.. i need to trigger it on textfield thanks.Fifi
This is different than the OP's question, but you can use something like the SDCAlertView Cocoapod, which optionally lets you allow an outside tap to dismiss.Notochord
Note that even though on iPad, the cancel action will not be displayed in the controller, its completion handler is still called.Skeptic
F
16

The solution which works for UIAlertController with alert style. Just needed to add gesture recognizer to alertController superview.

    [self presentViewController: alertController
                   animated: YES
                 completion:^{
                     alertController.view.superview.userInteractionEnabled = YES;
                     [alertController.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(alertControllerBackgroundTapped)]];
                 }];

- (void)alertControllerBackgroundTapped
{
    [self dismissViewControllerAnimated: YES
                             completion: nil];
}
Freestone answered 29/10, 2015 at 17:46 Comment(0)
F
1

UITapGestureRecognizer didn't work for me so I've used this way:

func addDismissControl(_ toView: UIView) {
    let dismissControl = UIControl()
    dismissControl.addTarget(self, action: #selector(self.dismissAlertController), for: .touchDown)
    dismissControl.frame = toView.superview?.frame ?? CGRect.zero
    toView.superview?.insertSubview(dismissControl, belowSubview: toView)
}

func dismissAlertController() {
    self.dismiss(animated: true, completion: nil)
}

func presentAlertController(title: String?, message: String?, preferredStyle: UIAlertControllerStyle,  handler: ((UIAlertAction) -> Swift.Void)? = nil, completion: (() -> Swift.Void)? = nil) {

    let alertController = UIAlertController(title: title, message: nil, preferredStyle: .actionSheet)
    alertController.addAction(UIAlertAction(title: "OK", style: .default) { (alertAction) -> Void in
        handler?(alertAction)
    })

    self.present(alertController, animated: true, completion: {
        self.addDismissControl(alertController.view)
        completion?()
    })
}

func someWhereInYourViewController() {
    // ...
    presentAlertController(title: "SomeTitle", message: "SomeMessage", preferredStyle: .actionSheet, handler: { (alertAction) -> Void in
        //do some action
    }, completion: {
        //do something after presentation
    })
    // ...
}
Ferric answered 4/7, 2017 at 15:43 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.