Iphone SDK dismissing Modal ViewControllers on ipad by clicking outside of it

拟墨画扇 提交于 2019-11-26 05:59:42

问题


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?


回答1:


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.




回答2:


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




回答3:


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



回答4:


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.




回答5:


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



回答6:


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



回答7:


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.




回答8:


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.




回答9:


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




回答10:


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



回答11:


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.




回答12:


I know it's late but consider using CleanModal (tested with iOS 7 and 8).

https://github.com/orafaelreis/CleanModal




回答13:


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



回答14:


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


来源:https://stackoverflow.com/questions/2623417/iphone-sdk-dismissing-modal-viewcontrollers-on-ipad-by-clicking-outside-of-it

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!