54

当用户在模态视图之外点击时,我想关闭 FormSheetPresentation 模态视图控制器......我已经看到一堆应用程序这样做(例如 ipad 上的 ebay),但我不知道如何因为下面的视图被禁用触摸当模态视图像这样显示时(他们是否将其呈现为弹出窗口?)......有人有什么建议吗?

4

14 回答 14

128

我迟到了一年,但这很简单。

让您的模态视图控制器将手势识别器附加到视图的窗口:

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

处理代码:

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

就是这样。该死的HIG,这是一种有用且通常是直观的行为。

于 2011-05-30T20:40:55.200 回答
12

对于 iOS 8,您必须同时实现UIGestureRecognizer, 并在横向时交换点击位置的 (x,y) 坐标。不确定这是否是由于 iOS 8 错误。

- (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;
}
于 2014-09-11T20:38:28.440 回答
11

如果其他应用程序允许通过单击外部视图来关闭视图,则它们不会使用模态视图。 UIModalPresentationFormSheets不能以这种方式解雇。(SDK3.2 中的任何 UIModal 也不能)。只有在UIPopoverController区域外单击才能解除。应用程序开发人员很有可能(尽管针对 Apple 的 iPad HIG)遮蔽了背景屏幕,然后将其显示为UIPopoverController看起来像UIModalPresentationFormSheets(或其他 UIModal 视图)。

[...] UIModalPresentationCurrentContext 样式让视图控制器采用其父级的呈现样式。在每个模态视图中,灰色区域显示底层内容,但不允许点击该内容。因此,与弹出框不同,您的模态视图必须仍然具有允许用户关闭模态视图的控件。

有关更多信息,请参阅开发人员站点上的 iPadProgrammingGuide(第 46 页——“配置模态视图的演示样式”)

于 2010-04-15T04:42:46.843 回答
10

上面的代码效果很好,但我会将 if 语句更改为,

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

这确保您仍然可以与导航栏交互,否则点击它会关闭模式视图。

于 2012-10-19T12:29:18.857 回答
10

针对 iOS 8 更新的答案

显然,在 iOS 8 中,UIDimmingView有一个点击手势识别器,它会干扰初始实现,所以我们忽略它并且不要求它失败。


这是速度的时代,所以大多数人可能只是复制上面的代码。但是,不幸的是,我在代码方面患有强迫症。

这是一个模块化解决方案,它使用 Danilo Campos 的答案和 categories。它还解决了一个重要的错误,如果您通过其他方式解除您的模式,可能会出现,如前所述

注意: if 语句在那里,因为我使用 iPhone 和 iPad 的视图控制器,只有 iPad 需要注册/取消注册。

更新:要点已更新,因为它无法与出色的FCOverlay代码一起正常工作,并且不允许在呈现的视图中识别手势。这些问题是固定的。使用该类别很简单:

- (void)viewDidAppear:(BOOL)animated
{
    [super viewDidAppear:animated];

    if (self.presentingViewController) {
        [self registerForDismissOnTapOutside];
    }
}

- (void)viewWillDisappear:(BOOL)animated
{
    if (self.presentingViewController) {
        [self unregisterForDismissOnTapOutside];
    }

    [super viewWillDisappear:animated];
}
于 2014-05-08T13:12:59.927 回答
8

将此代码复制粘贴到您的 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];
        }
    }
}
于 2012-09-17T07:21:14.090 回答
3

Very important:如果您有任何其他方式来关闭模态弹出窗口,请不要忘记删除点击手势识别器!

我忘记了这一点,后来又发生了疯狂的崩溃,因为点击识别器仍在触发事件。

于 2014-01-06T17:53:45.947 回答
2

根据 Apple 的 iOS HIG,1. 模态视图本身不具备在没有任何输入的情况下被关闭的能力;2. 在需要用户输入的情况下使用模态视图。

于 2011-09-19T00:26:42.807 回答
1

改用 UIPresentationController:

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

从 LookInside 示例修改

于 2014-09-12T11:55:09.380 回答
1

这适用于 ios7 和 8 和导航栏。

如果您不需要导航栏,只需在管道之后的 if 语句中删除 location2 和第二个条件。

@MiQUEL 这也应该对你有用

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

编辑:您可能还需要成为手势识别器代表才能使此解决方案和其他上述解决方案起作用。这样做:

@interface CommentTableViewController () <UIGestureRecognizerDelegate>

将自己设置为识别器的代表:

self.recognizer.delegate = self;

并实现这个委托方法:

- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
    return YES;
}
于 2014-10-10T05:34:37.530 回答
0

这是相当可行的。

看看这里

https://stackoverflow.com/a/26016458/4074557

它是一个为 ipad 自动关闭的 NavigationController(模态)(当您在外面点击时)

在其中使用您的视图控制器。

希望能帮助到你。

于 2014-09-24T12:25:28.673 回答
0

我知道已经很晚了,但考虑使用 CleanModal(在 iOS 7 和 8 上测试过)。

https://github.com/orafaelreis/CleanModal

于 2014-11-17T16:36:22.850 回答
0

您可以像这样使用MZFormSheetController

MZFormSheetController *formSheet = [[MZFormSheetController alloc] initWithSize:customSize viewController:presentedViewController];
formSheet.shouldDismissOnBackgroundViewTap = YES;
[presentingViewController mz_presentFormSheetController:formSheet animated:YES completionHandler:nil];
于 2015-01-22T11:05:51.237 回答
0

在 Swift 2/Xcode 版本 7.2 (7C68) 中,以下代码对我有用。

注意:这段代码应该放在呈现的FormSheet或Page Sheet的ViewController.swift文件中,这里:“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)
            }
        }
    }
}
于 2016-01-24T19:38:05.240 回答