Uialertcontroller Handle Dismiss Upon Click Outside (Ipad)

UIAlertController handle dismiss upon click outside (IPad)

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];
}

How to dismiss UIAlertController when tap outside the UIAlertController?

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
}))

Don't dismiss uialertcontroller sheet with tap gesture outside dialog area in iOS

for UIAlertController Type as alert

you can download the sample project

add the gesture recognizer to alertController superview for handle the userinteraction

self.present(alertController, animated: true, completion: {() -> Void in
alertController.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: nil)
})

on that action do nothing

update

let alertController = UIAlertController(title: "Do something", message: "With this", preferredStyle: .alert)
alertController.addAction(UIAlertAction(title: "Done", style: .default) { action in
// perhaps use action.title here
})

self.present(alertController, animated: true, completion: {() -> Void in

alertController.view.superview?.addGestureRecognizer(UITapGestureRecognizer(target: self, action: nil))
})

for UIAlertController Type as actionSheet

you can download the sample project

you can do this two ways

option 1

 alert.view.superview.subviews[0] isUserInteractionEnabled = false

option 2

   alert.view.superview?.subviews[0].addGestureRecognizer(UITapGestureRecognizer(target: self, action: nil))

for e.g

   self.present(sheet, animated: true, completion: {() -> Void in
// sheet.view.superview?.subviews[0].isUserInteractionEnabled = false;
sheet.view.superview?.subviews[0].addGestureRecognizer(UITapGestureRecognizer(target: self, action: nil))

})

full code

  let sheet = UIAlertController(title: "karthik", message: "check with", preferredStyle: .actionSheet)

sheet.addAction(UIAlertAction(title: "Create Ticket", style: .default) { (action: UIAlertAction) in

})

sheet.addAction(UIAlertAction(title: "Start VM", style: .default) { (action: UIAlertAction) in

})

sheet.addAction(UIAlertAction(title: "Restart VM", style: .default) { (action: UIAlertAction) in

})

sheet.addAction(UIAlertAction(title: "Stop VM", style: .destructive) { (action: UIAlertAction) in

})

sheet.addAction(UIAlertAction(title: "Cancel", style: .cancel) { (action: UIAlertAction) in

})

self.present(sheet, animated: true, completion: {() -> Void in
// sheet.view.superview?.subviews[0].isUserInteractionEnabled = false;
sheet.view.superview?.subviews[0].addGestureRecognizer(UITapGestureRecognizer(target: self, action: nil))

})

How to restrict alert to dismiss after button click?

From your code, I tested this in line with my comment:

let okAction = UIAlertAction(title: "OK", style: UIAlertActionStyle.Default) {
UIAlertAction in
NSLog("OK Pressed")
DispatchQueue.main.async { () -> Void in
self.testAlert(sender) // Or whatever functionalists that created the alertController
}
}

In iOS, how do I close an Alert Controller by tapping INSIDE the Alert popup?

The simplest trick I found on SO seemed to be to add a UIControl with an action target that dismisses the AlertController on any touch event. But the UIControl requires a frame CGRect for it to function properly.

Here are the important points for the UIControl:

let dismissControl = UIControl()

// make the dismissControl execute dismissAlert for all touch events
dismissControl.addTarget(self, action: #selector(self.dismissAlert), for: .allTouchEvents)

// NOTE: must use the bounds, not the frame for it to work
self.dismissControl.frame = self.alertController.view.bounds

// add the UIControl on top of the UIAlertControl view
self.alertController.view.addSubview(self.dismissControl)

I explored 4 different possibilities:

  • Timeout - dismiss an Alert Window after a number of seconds (like Android Toast)
  • Tap outside the Alert Window (tap on Alert Window DOES NOT close it)
  • Tap anywhere on the screen to close it (on Alert Window or outside of it)
  • Tap the Alert Window (tap anywhere else DOES NOT close it)

And here is a complete ViewController (Xcode 9.2, Swift 4) that handles all 4 cases:

//
// ViewController.swift
// AlertDemo - Display and dismiss UIAlertControllers with UIControl (no action buttons)
// (similar to Toast on Android OS)
//
// 4 different ways to close a UIAlertController popup window:
//
// 1) Timeout - dismiss an Alert Window after a number of seconds (like Android Toast)
// 2) Tap outside the Alert Window (tap on Alert Window DOES NOT close it)
// 3) Tap the Alert Window (tap anywhere else DOES NOT close it)
// 4) Tap anywhere on the screen (on Alert Window or outside of it) to close it

//
// Created by ByteSlinger on 2018-01-19.
// Copyright © 2018 ByteSlinger. All rights reserved.
//

import UIKit

class ViewController: UIViewController {
let alertController = UIAlertController(title: "Alert", message: "SO Awesome!", preferredStyle: .alert)
let timeoutController = UIAlertController(title: "Timeout", message: "That Alert timed out!", preferredStyle: .actionSheet)
let dismissControl = UIControl()

@IBOutlet var alertButton1: UIButton!
@IBOutlet var alertButton2: UIButton!
@IBOutlet var alertButton3: UIButton!
@IBOutlet var alertButton4: UIButton!

// display a modal popup in the middle, wait for timeout to close
@IBAction func displayAlert1(_ sender: UIButton) {
alertController.message = "You must wait for this Alert to timeout"

self.present(alertController, animated: true, completion: {
self.perform(#selector(self.timeoutAlert), with: self.alertController, afterDelay: 3)
})
}

// display a modal popup in the middle, tap outside popup to close
@IBAction func displayAlert2(_ sender: UIButton) {
alertController.message = "Tap outside this Alert to close"

self.present(alertController, animated: true, completion: {
self.dismissControl.frame = self.alertController.view.superview?.bounds ?? CGRect.zero

self.alertController.view.superview?.insertSubview(self.dismissControl, belowSubview: self.alertController.view)

self.perform(#selector(self.timeoutAlert), with: self.alertController, afterDelay: 3)
})
}

// display a modal popup in the middle, tap on or outside popup to close
@IBAction func displayAlert3(_ sender: UIButton) {
alertController.message = "Tap anywhere to close"

self.present(alertController, animated: true, completion: {
self.dismissControl.frame = self.alertController.view.superview?.bounds ?? CGRect.zero

self.alertController.view.superview?.addSubview(self.dismissControl)

self.perform(#selector(self.timeoutAlert), with: self.alertController, afterDelay: 3)
})
}

// display a modal popup in the middle, tap on popup to close
@IBAction func displayAlert4(_ sender: UIButton) {
alertController.message = "Tap on this Alert to close"

self.present(alertController, animated: true, completion: {
// important - use bounds: alertController.view.frame WILL NOT WORK
self.dismissControl.frame = self.alertController.view.bounds

self.alertController.view.addSubview(self.dismissControl)

self.perform(#selector(self.timeoutAlert), with: self.alertController, afterDelay: 3)
})
}

// close the current alert popup (middle) and display the timeout alert (bottom)
@objc func timeoutAlert(_ alertController: UIAlertController) {
if (alertController == UIApplication.shared.keyWindow?.rootViewController?.presentedViewController) {
timeoutController.message = alertController.message!

alertController.view.willRemoveSubview(self.dismissControl)

alertController.dismiss(animated: true, completion: {
self.present(self.timeoutController,animated: true, completion: {
self.perform(#selector(self.dismissTimeout), with: self.timeoutController, afterDelay: 2)
})
})
}
}

// dimiss (close) the alert popup
@objc func dismissAlert() {
// make sure there are no timeoutAlert calls waiting
NSObject.cancelPreviousPerformRequests(withTarget: self)

alertController.view.willRemoveSubview(self.dismissControl)

alertController.dismiss(animated: true, completion: nil)
}

// dimiss (close) the timeout popup
@objc func dismissTimeout(_ alert: UIAlertController) {
alert.dismiss(animated: true, completion: nil)
}

override func viewDidLoad() {
super.viewDidLoad()

// make the dismissControl execute dismissAlert for all touch events
dismissControl.addTarget(self, action: #selector(self.dismissAlert), for: .allTouchEvents)

// make the buttons a little prettier
alertButton1.layer.cornerRadius = 32
alertButton2.layer.cornerRadius = 32
alertButton3.layer.cornerRadius = 32
alertButton4.layer.cornerRadius = 32
}
}

To use this, just create 4 UIButtons in IB and connect them to the @IBOutlet vars and functions.

NOTE: I had to cancel perform requests or sometimes the alerts would get cancelled by the previous timeout. This line in dismissAlert() did the trick:

NSObject.cancelPreviousPerformRequests(withTarget: self)

Here is the full Xcode project on GitHub: AlertDemo

Thanks to @Apoc and his inspiration with UIControl in this post: UIAlertController handle dismiss upon click outside (IPad). Setting the UIControl frame from the UIAlertController bounds was what finally got it to work.



Related Topics



Leave a reply



Submit