64

How to dismiss UIAlertController when tap outside the UIAlertController?

I can add a UIAlertAction of style UIAlertActionStyleCancel to dismiss the UIAlertController.

But I want to add the function that when user tap outside the UIAlertController the UIAlertController will dismiss. How to do that? Thank you.

Ashish Kakkad
  • 23,586
  • 12
  • 103
  • 136
leizh00701
  • 1,893
  • 5
  • 21
  • 32
  • 2
    Hi Leizh00701 check this out http://stackoverflow.com/questions/25466718/uialertcontroller-handle-dismiss-upon-click-outside-ipad – Harish May 06 '15 at 11:53
  • what the sender is :UIControl *aControl = (UIControl *) sender; – leizh00701 May 06 '15 at 11:55
  • 1
    UIControl is the base class for control objects such as buttons and sliders that convey user intent to the application Please check this out https://developer.apple.com/library/ios/documentation/UIKit/Reference/UIControl_Class/index.html – Harish May 06 '15 at 12:04

11 Answers11

54

Add a separate cancel action with style UIAlertActionStyleCancel. So that when user taps outside, you would get the callback.

Obj-c

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:@"Alert Title" message:@"A Message" preferredStyle:UIAlertControllerStyleActionSheet];
[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
 // Called when user taps outside
}]];

Swift 5.0

let alertController = UIAlertController(title: "Alert Title", message: "A Message", preferredStyle: .actionSheet)             
alertController.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: { 
    action in
         // Called when user taps outside
}))
Ely Dantas
  • 705
  • 11
  • 23
Vivek Yadav
  • 1,117
  • 10
  • 14
  • This would not be standard behavior for Alert dialogs as they are designed to always be modal. – Kudit Oct 02 '15 at 01:32
  • On iPhone UIAlertActionStyleCancel is shown (and tap doesn't work), and on iPad is hidden because it's replaced with a tap over view. – 93sauu Oct 26 '16 at 12:20
  • 19
    Your Swift 5.0 version simply adds a "Cancel" button that you can asign code to but tapping outside the alert doesn't trigger anything, you have to click on the button. – Neph Dec 18 '19 at 11:18
  • Same of me. The implementation above doesn't make the dialog dismiss on touch outside of it – Bréndal Teixeira Dec 23 '19 at 20:39
54

If you are targeting devices having iOS > 9.3 and using Swift and preferredStyle is Alert you can use snippet as below:

func showAlertBtnClicked(sender: UIButton) {
    let alert = UIAlertController(title: "This is title", message: "This is message", preferredStyle: .Alert)
    self.presentViewController(alert, animated: true, completion:{
        alert.view.superview?.userInteractionEnabled = true
        alert.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alertControllerBackgroundTapped)))
    })
}

func alertControllerBackgroundTapped()
{
    self.dismissViewControllerAnimated(true, completion: nil)
}

With swift 3:

func showAlertBtnClicked(sender: UIButton) {
    let alert = UIAlertController(title: "This is title", message: "This is message", preferredStyle: .alert)
    self.present(alert, animated: true) {
        alert.view.superview?.isUserInteractionEnabled = true
        alert.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.alertControllerBackgroundTapped)))
    }
}

func alertControllerBackgroundTapped()
{
    self.dismiss(animated: true, completion: nil)
}
chancyWu
  • 14,073
  • 11
  • 62
  • 81
  • 15
    As of iOS 9.3, you have to add the gesture recognizer to the second subview of the superview (`alert.view.superview.subviews[1]`) instead of the superview itself. As stated in the documentation, the view hierarchy for UIAlertController is private so there is no guaranty it will not change in future iOS releases. – Thanh Pham Jul 28 '16 at 11:02
  • 1
    I have mixed your answers with sfongxing10000's, and I came up with adding the tap gesture to the `alert.view.superview` and all of its `subviews`. It's working for now, iOS 11.2! – FonzTech Dec 29 '17 at 01:08
  • 2
    Not sure if this is a change in Swift 5 but you have to add `@objc` to the `alertControllerBackgroundTapped` func, otherwise Xcode'll explain. Other than that the Swift 3 version also works with Swift 5, thanks. – Neph Dec 18 '19 at 11:27
41

Swift, Xcode 9

Dismiss AlertController with cancel button

provide action to your alertController where UIAlertAction's style is .cancel

let cancelAction = UIAlertAction(title: "Cancel", style: .cancel, handler: nil)
alertController.addAction(cancelAction)

Using this method alertController will be dismissed when user will tap to cancel action button as well as outside of the alertController.

if you don't want user to dismiss alertController after touch up outside of alertController, disable user interaction of first subviews of alertController in completion closure of present method.

self.present(alertController, animated: true) {
     alertController.view.superview?.subviews[0].isUserInteractionEnabled = false
    }

Dismiss AlertController on touchup outside of Controller view

If you don't want cancel button in your controller view and want to dismiss controller when user touchup outside of controller view, do so

self.present(alertController, animated: true) {
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(self.dismissAlertController))
        alertController.view.superview?.subviews[0].addGestureRecognizer(tapGesture)
}

@objc func dismissAlertController(){
    self.dismiss(animated: true, completion: nil)
}
  • "Using this method alertController will be dismissed when user will tap to cancel action button as well as outside of the alertController." I don't believe this is true, when you tap outside alert controller alert, it does not resign the first responder. It only resigns the first responder when tapping outside of the alertController when using an action sheet. – stromyc Oct 26 '19 at 19:14
11

If you are using Swift :

Add an action with addAction(_:) and style:UIAlertActionStyle.Cancel.

The `handler will be called when ou tap on the button or outside the frame.

var alertVC = UIAlertController(...) // initialize your Alert View Controller

        alertVC.addAction(UIAlertAction(title: "Cancel", style: UIAlertActionStyle.Cancel, handler: {
            (alertAction: UIAlertAction!) in
            alertVC.dismissViewControllerAnimated(true, completion: nil)
        }))

Objective-C :

UIAlertController *alertController = [UIAlertController alertControllerWithTitle:...];


[alertController addAction:[UIAlertAction actionWithTitle:@"Cancel" style:UIAlertActionStyleCancel handler:^(UIAlertAction *action) {
   [alertVC dismissViewControllerAnimated:YES completion:nil];
}]];
DeyaEldeen
  • 10,847
  • 10
  • 42
  • 75
Kevin Machado
  • 4,141
  • 3
  • 29
  • 54
6

Swift 4:

Dismiss Action Sheet when User Taps outside Action Sheet created using UIAlertController

Code Snippet:

// Declare Action Sheet reference
var actionSheet: UIAlertController!

// Init and Show Action Sheet
func showActionSheetClicked(sender: UIButton) {

    // Init Action Sheet
    actionSheet = UIAlertController(title: "Title", message: "Message", preferredStyle: .actionSheet)

    self.present(actionSheet, animated: true) {
        // Enabling Interaction for Transperent Full Screen Overlay
        self.actionSheet.view.superview?.subviews.first?.isUserInteractionEnabled = true

        // Adding Tap Gesture to Overlay
        self.actionSheet.view.superview?.subviews.first?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: #selector(self.actionSheetBackgroundTapped)))
    }
}

// To dismiss Action Sheet on Tap
@objc func actionSheetBackgroundTapped() {
    self.actionSheet.dismiss(animated: true, completion: nil)
}
2

The easiest way in Obj-C:

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

and then:

- (void)alertControllerBackgroundTapped
{
    [self dismissViewControllerAnimated: YES
                             completion: nil];
}
oskarko
  • 3,382
  • 1
  • 26
  • 26
2
- (void)addBackgroundDismissTapForAlert:(UIAlertController *)alert {
    if (!alert.view.superview) {
        return;
    }
    alert.view.superview.userInteractionEnabled = YES;
    [alert.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(alertControllerBackgroundTapped)]];
    for (UIView *subV in alert.view.superview.subviews) {
        if (subV.width && subV.height) {
            subV.userInteractionEnabled = YES;
            [subV addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(alertControllerBackgroundTapped)]];
        }
    }
}
- (void)alertControllerBackgroundTapped {

    [self dismissViewControllerAnimated: YES
                         completion: nil];
}
Ram Koti
  • 2,203
  • 7
  • 26
  • 36
1
    UIView *alertView = self.alertController.view;
UIView *superPuperView = self.alertController.view.superview;
CGPoint tapCoord = [tap locationInView:superPuperView];
if (!CGRectContainsPoint(alertView.frame, tapCoord)) {
    //dismiss alert view
}
Slashik
  • 325
  • 1
  • 4
1

If you view debug the superview of the alert, you see its not as simple as adding a tap gesture recognizer to the UITransitionView of the _UIAlertControllerView.You can do this instead

[presenter presentViewController:alertController animated:YES completion:^{
    NSArray <UIView *>* superviewSubviews = alertController.view.superview.subviews;
    for (UIView *subview in superviewSubviews) {
        if (CGRectEqualToRect(subview.bounds, weakSelf.view.bounds)) {
            [subview addSingleTapGestureWithTarget:weakSelf action:@selector(dismissModalTestViewController)];
        }
    }
}];
Michael Lorenzo
  • 628
  • 10
  • 20
1

On iOS 15 it appears the view hierarchy for UIAlertController has changed yet again. It's presented as a new UIWindow that contains the controller itself. So in order to dismiss on tap outside:

present(alertController, animated: true) { [weak self] in
    guard let self = self else { return }

    let dismissGesture = UITapGestureRecognizer(target: self, action: #selector(self.shouldDismiss))

    self.alertController.view.window?.isUserInteractionEnabled = true
    self.alertController.view.window?.addGestureRecognizer(dismissGesture)
}

and for shouldDismiss function:

@objc private func shouldDismiss() {
    alertController.dismiss(animated: true)
}
geoff
  • 2,251
  • 1
  • 19
  • 34
0

The simplest way:

- (void)viewDidLoad {
    [super viewDidLoad];

    [self button];

}

- (void) button {
    UIButton * AlertButton = [UIButton buttonWithType:UIButtonTypeSystem];
    [AlertButton setTitle:@"Button" forState:UIControlStateNormal];
    AlertButton.frame = CGRectMake((self.view.frame.size.width/2) - 50 , (self.view.frame.size.height/2) - 25, 100, 50);
    [AlertButton addTarget:self action:@selector(Alert) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:AlertButton];
}

- (void)Alert {
    UIAlertController * alert = [UIAlertController alertControllerWithTitle:@"Alert Title" message:@"Alert Message" preferredStyle:UIAlertControllerStyleAlert];
    [self presentViewController: alert animated: YES completion:^{ alert.view.superview.userInteractionEnabled = YES; [alert.view.superview addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget: self action: @selector(DismissAlertByTab)]]; }];
}

- (void)DismissAlertByTab
{
    [self dismissViewControllerAnimated: YES completion: nil];
}
Ibrahim
  • 6,006
  • 3
  • 39
  • 50