I want to dismiss a FormSheetPresentation modal view controller when the user taps outside the modal view...I have seen a bunch of apps doing this (ebay on ipad for example) but i cant figure out how since the underneath views are disabled from touches when modal views are displayed like this (are they presenting it as a popover perhaps?)...anyone have any suggestions?
14 Answers
I'm a year late, but this is pretty straightforward to do.
Have your modal view controller attach a gesture recognizer to the view's window:
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
[recognizer release];
The handling code:
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}
That's about it. HIG be damned, this is a useful and often intuitive behavior.

- 643
- 1
- 10
- 19

- 6,430
- 2
- 25
- 19
-
I have a modalViewController with navigationController and this codes isn't working. I have also tried to pair gesture event to self.view.parentViewController.view with no luck. Need further help... – Borut Tomazin Aug 25 '11 at 07:06
-
2Test to put the UITapGestureRecognizer initialization code in _viewDidAppear:_ – Olof Aug 31 '11 at 15:18
-
281. It did not work when I initialized gestureRecognizer in viewDidLoad, but did work when I initialized in viewDidAppear. – Jon Oct 13 '11 at 23:37
-
22. ModalViews usually have delegates associated with them. Be sure to notify the delegate that you dismissed. Also, if there is any other way the modal view is dismissed, then you need a centralized method to remove the gesture recognizer – Jon Oct 14 '11 at 00:41
-
2Be aware of incompatibilities between iOS 4 and iOS 5 here; in iOS 5 the recognizer must be added in `viewDidAppear`, as mentioned above; on iOS 4 this is not necessary. Be aware also that the behaviour of `parentViewController` [changed in iOS 5](http://omegadelta.net/2011/11/04/oh-my-god-they-killed-parentviewcontroller/). – edsko Mar 23 '12 at 11:14
-
1I have to add `- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { return YES; }` (in the recognizer's delegate) to make other gesture recognizers workable. – adruzh Aug 20 '13 at 05:00
-
working perfect for me :). I have used this code in Titanium appcelerator module, so, I need to make some modifications. – Paresh Thakor Jun 18 '14 at 07:18
-
15This has stopped working for me on iOS8 / XCode 6(4) !! – NathofGod Jul 22 '14 at 20:29
-
1In iOS 8, I use UIPresentationController instead, see WWDC 2014 code example:"LookInside: Presentation Controllers Adaptivity and Custom Animator Objects", add tap gesture in UIPresentationController's containerView. – yhlin Sep 10 '14 at 06:46
-
@yhlin can you give a code example? Tried using `[self.presentationController.containerView.window addGestureRecognizer:recognizer];` in `viewDidLoad` to no avail. – Erich Sep 10 '14 at 14:34
-
3Change the location code to: "CGPoint location = [tapSender locationInView: self.presentingViewController.view];" – orafaelreis Oct 16 '14 at 19:13
-
If your modal happens to have a UITableView, I recommend setting delaysTouchesEnded to NO on the gestureRecognizer to fix longer presses on cells (on iOS 7) – Arie Litovsky Nov 04 '14 at 17:54
-
This also doesn't work if you place the code in _viewWillAppear_ – nsinvocation Dec 24 '14 at 09:29
-
1on iOS8 the delegate must be implemented and YES should be returned for ```gestureRecognizerShouldBegin```, ```shouldRecognizeSimultaneouslyWithGestureRecognizer``` and ```shouldReceiveTouch```. – dwery Aug 31 '15 at 22:24
-
This works just fine in ios 9.2 and below. If you are having problems detecting the tap, add the gesture recognizer from storyboard or xib don't forget the cancel touches in view – Matt Feb 09 '16 at 22:17
-
What is the need for the `[self.view.window removeGestureRecognizer:sender];` ? It works just as fine without it. – Matt Feb 09 '16 at 22:20
For iOS 8, you must both implement the UIGestureRecognizer
, and swap the (x,y) coordinates of the tapped location when in landscape orientation. Not sure if this is due to an iOS 8 bug.
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
// add gesture recognizer to window
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
recognizer.delegate = self;
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded) {
// passing nil gives us coordinates in the window
CGPoint location = [sender locationInView:nil];
// swap (x,y) on iOS 8 in landscape
if (SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(@"8.0")) {
if (UIInterfaceOrientationIsLandscape([UIApplication sharedApplication].statusBarOrientation)) {
location = CGPointMake(location.y, location.x);
}
}
// convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil]) {
// remove the recognizer first so it's view.window is valid
[self.view.window removeGestureRecognizer:sender];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
#pragma mark - UIGestureRecognizer Delegate
- (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch
{
return YES;
}

- 2,509
- 2
- 21
- 24
-
1I've found a problem with this code. If there is a button in the view and the keyboard is shown, when I click the button the UITapGestureRecognizer interprets wrong coordinate (everything has been moved when the keyboard appears) so the view gets dismissed. – MiQUEL Sep 27 '14 at 00:37
-
1hi i can't seems to detect tap outside the "presented" view, but able to detect tap within the "presented" view – chrizonline Oct 18 '14 at 07:09
-
@Erich I found that this works in the opposite way that it is supposed to. It detects taps inside the presented modal view but not outside it. – motionpotion Oct 22 '14 at 09:34
The other apps are not using Modal Views if they allow the view to be dismissed by clicking outside of it. UIModalPresentationFormSheets
cannot be dismissed this way. (nor, indeed can any UIModal in SDK3.2). Only the UIPopoverController
can be dismissed by clicking outside of the area. It is very possible (though against Apple's iPad HIG) for the app developer to have shaded out the background screen and then displayed the UIPopoverController
so that it looks like a UIModalPresentationFormSheets
(or other UIModal View).
[...] UIModalPresentationCurrentContext style lets a view controller adopt the presentation style of its parent. In each modal view, the dimmed areas show the underlying content but do not allow taps in that content. Therefore, unlike a popover, your modal views must still have controls that allow the user to dismiss the modal view.
See the iPadProgrammingGuide on the developer site for more information (Page 46 -- "Configuring the Presentation Style for Modal Views")

- 2,214
- 3
- 28
- 45
-
1Apple suggests a modal should contain its own close controls, but then contradicts that by having touch-outside-modal-to-dismiss within the iTunes app. the GAP app does the same thing – CVertex Apr 25 '10 at 13:17
The code above works great, but I would change the if statement to,
if (!([self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil] || [self.navigationController.view pointInside:[self.navigationController.view convertPoint:location fromView:self.navigationController.view.window] withEvent:nil]))
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
This makes sure you can still interact with the navigation bar, otherwise tapping in it dismisses the modal view.

- 53,910
- 52
- 193
- 240

- 101
- 1
- 2
Answer updated for iOS 8
Apparently, in iOS 8, the UIDimmingView
has a tap gesture recognizer, which interferes with the initial implementation, so we ignore it and don't require it to fail.
This is the age of speed, so most are probably just copying the code above.. But, I suffer from OCD when it comes to code, unfortunately.
Here is a modular solution that uses Danilo Campos's answer with categories. It also solves an important bug that may occur if you are dismissing your modal through other means, as mentioned.
NOTE: The if statements are there because I use the view controller for both iPhone and iPad, and only the iPad needs to register/unregister.
UPDATE: The gist has been updated, since it didn't work properly with the awesome FCOverlay code, and it didn't allow gestures to be recognized in the presented view. Those issues are fixed. Using the category is as easy as:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.presentingViewController) {
[self registerForDismissOnTapOutside];
}
}
- (void)viewWillDisappear:(BOOL)animated
{
if (self.presentingViewController) {
[self unregisterForDismissOnTapOutside];
}
[super viewWillDisappear:animated];
}
-
In your linked code, I would change the line `[gesture locationInView:nil]` to `[gesture locationInView:view]`, then remove the line that converts the point. This seems to work better in iOS8. – Arie Litovsky Nov 04 '14 at 17:33
-
@ArieLitovsky Thanks, you're absolutely right. Looking at my own project, I've already made that change, but didn't update the Gist since I started using project dependencies, like BlocksKit. – Mazyod Nov 05 '14 at 08:05
Copy paste this code in your ModalViewController :
- (void) viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
//Code for dissmissing this viewController by clicking outside it
UITapGestureRecognizer *recognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleTapBehind:)];
[recognizer setNumberOfTapsRequired:1];
recognizer.cancelsTouchesInView = NO; //So the user can still interact with controls in the modal view
[self.view.window addGestureRecognizer:recognizer];
}
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location = [sender locationInView:nil]; //Passing nil gives us coordinates in the window
//Then we convert the tap's location into the local view's coordinate system, and test to see if it's in or outside. If outside, dismiss the view.
if (![self.view pointInside:[self.view convertPoint:location fromView:self.view.window] withEvent:nil])
{
// Remove the recognizer first so it's view.window is valid.
[self.view.window removeGestureRecognizer:sender];
[self dismissModalViewControllerAnimated:YES];
}
}
}

- 2,315
- 30
- 37
-
1I meant that your answer is the same, comments included, as the approved one on this Q. – dwery Nov 04 '13 at 00:03
-
1viewDidAppear is my add. You can down vote. Moderators can see their job ! – Samidjo Nov 04 '13 at 17:19
Very important:
If you have any other way to close your modal popup window, don't forget to remove the tap gesture recognizer!
I forgot this, and got crazy crashes later on, since the tap recognizer was still firing events.

- 4,488
- 1
- 29
- 31

- 531
- 4
- 14
Accoring to Apple's iOS HIG, 1. the modal view doesn't have that ability to be dismissed without any input on itself; 2. use modal view in the situation that a user input is required.

- 1,484
- 2
- 18
- 33
Use UIPresentationController instead:
- (void)presentationTransitionWillBegin
{
[super presentationTransitionWillBegin];
UITapGestureRecognizer *dismissGesture=[[UITapGestureRecognizer alloc]initWithTarget:self action:@selector(dismissGestureTapped:)];
[self.containerView addGestureRecognizer:dismissGesture];
[[[self presentedViewController] transitionCoordinator] animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
} completion:nil];
}
- (void) dismissGestureTapped:(UITapGestureRecognizer *)sender{
if (sender.state==UIGestureRecognizerStateEnded&&!CGRectContainsPoint([self frameOfPresentedViewInContainerView], [sender locationInView:sender.view])) {
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
}
Modified from LookInside example

- 189
- 2
- 9
This works for me for ios7 an 8 and navigation bar.
If you don't need the nav bar just remove location2 and second condition in the if statement after the pipes.
@MiQUEL this should work for you too
- (void)handleTapBehind:(UITapGestureRecognizer *)sender
{
if (sender.state == UIGestureRecognizerStateEnded)
{
CGPoint location1 = [sender locationInView:self.view];
CGPoint location2 = [sender locationInView:self.navigationController.view];
if (!([self.view pointInside:location1 withEvent:nil] || [self.navigationController.view pointInside:location2 withEvent:nil])) {
[self.view.window removeGestureRecognizer:self.recognizer];
[self dismissViewControllerAnimated:YES completion:nil];
}
}
}
Edit: You may also need to be a gesture recognizer delegate for this and other above solutions to work. Do it like so:
@interface CommentTableViewController () <UIGestureRecognizerDelegate>
set yourself as the delegate for the recognizer:
self.recognizer.delegate = self;
and implement this delegate method:
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES;
}

- 1,655
- 20
- 30
It is pretty doable.
Take a look here
https://stackoverflow.com/a/26016458/4074557
It is a NavigationController (modal) that auto dismiss for ipad (when you tap outside)
Use your viewcontroller inside of it.
Hope it helps.
I know it's late but consider using CleanModal (tested with iOS 7 and 8).

- 2,855
- 2
- 28
- 31
You can use MZFormSheetController like this:
MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];

- 2,962
- 1
- 23
- 33
In Swift 2/Xcode Version 7.2 (7C68) the following code worked for me.
Attention: this code should be put in the ViewController.swift file of the presented FormSheet or Page Sheet, here: "PageSheetViewController.swift"
class PageSheetViewController: UIViewController, UIGestureRecognizerDelegate {
override func viewDidAppear(animated: Bool) {
let recognizer = UITapGestureRecognizer(target: self, action:Selector("handleTapBehind:"))
recognizer.delegate = self
recognizer.numberOfTapsRequired = 1
recognizer.cancelsTouchesInView = false
self.view.window?.addGestureRecognizer(recognizer)
}
func gestureRecognizer(sender: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWithGestureRecognizer:UIGestureRecognizer) -> Bool {
return true
}
func handleTapBehind(sender:UIGestureRecognizer) {
if(sender.state == UIGestureRecognizerState.Ended){
var location:CGPoint = sender.locationInView(nil)
// detect iOS Version 8.0 or greater
let Device = UIDevice.currentDevice()
let iosVersion = Double(Device.systemVersion) ?? 0
let iOS8 = iosVersion >= 8
if (iOS8) {
// in landscape view you will have to swap the location coordinates
if(UIInterfaceOrientationIsLandscape(UIApplication.sharedApplication().statusBarOrientation)){
location = CGPointMake(location.y, location.x);
}
}
if(!self.view.pointInside(self.view.convertPoint(location, fromView: self.view.window), withEvent: nil)){
self.view.window?.removeGestureRecognizer(sender)
self.dismissViewControllerAnimated(true, completion: nil)
}
}
}
}

- 421
- 2
- 10