在iOS 8上,如何通过点击外部来关闭模态表单视图?

21

我一直在尝试在iOS 8上点击外部区域来关闭模态表单视图,但没有成功。我尝试了这段代码

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

但它无法检测到外部视图的点击,有什么建议吗?


我已经发布了我尝试过的代码链接。 - mac_019_0
发布您相关的代码,而不是其他人的代码。另外,“没有工作”不是一个有效的问题描述。 - Raptor
更新了问题,请看一下。 - mac_019_0
我还没有找到解决方案,但我正在关注这个讨论:https://dev59.com/imox5IYBdhLWcg3wmFWV - Martino Bonfiglioli
3个回答

38

iOS 8实际上存在两个问题。首先,手势识别没有开始。

我通过添加UIGestureRecognizerDelegate协议和实现解决了这个问题。

-(BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer*)otherGestureRecognizer
{
    return YES;
}

另外,不要忘记向XXX注册代表。

recognizer.delegate = self;

现在手势识别器应该能够识别手势并调用目标方法(handleTapBehind:)。

iOS 8中出现了第二个问题: 如果传递nil作为视图参数,locationInView:似乎没有考虑设备方向。相反,传递根视图可以解决这个问题。

这是我的目标代码,在 iOS 7.1 和 8.0 中似乎都能正常工作:

if (sender.state == UIGestureRecognizerStateEnded) {
    UIView *rootView = self.view.window.rootViewController.view;
    CGPoint location = [sender locationInView:rootView];
    if (![self.view pointInside:[self.view convertPoint:location fromView:rootView] withEvent:nil]) {
        [self dismissViewControllerAnimated:YES completion:^{
            [self.view.window removeGestureRecognizer:sender];
        }];
    }
}

我按照你的步骤操作,一切都很完美。但是,我不确定为什么我们需要首先添加“shouldRecognizeSimultaneouslyWithGestureRecognizer:”。如果我不返回“YES”,模态视图外部的轻击将无法被识别。但这与“RecognizeSimultaneouslyWithGestureRecognizer”有什么关系呢? - David Liu
@DavidLiu 另外还有一个(系统)手势识别器处于活动状态,它具有优先级。可能是一个错误。 - chris
2
这是最好的解决方案。并且解决了其他解决方案存在的旋转坐标问题。完美! - MiQUEL
你的解决方案和我的一样,但在iOS8上,在小屏幕iPhone上呈现全屏表单时,横向会使其全部偏移。不知何故,在iPhone上坐标的旋转根本不起作用。您可以在全屏视图中的任何位置轻触以将其关闭。但仅在横向上。它仍然在iPhone 6 Plus、iPad、iPhone纵向等上完美运行。尝试找到比仅使用if(iphone)或类似方法更好的检查方法。 - Ryan Poolos
在iOS 8.1中,几乎使用完全相同的代码,我可以使用self.view而不是self.window获取handleTapBehind。这可能是iOS 8.1与8.0之间的差异吗?(还是我的应用程序中有干扰因素?) - webjprgm
显示剩余2条评论

8
在iOS 8中,您可以考虑使用新的UIPresentationController类。它可以更好地控制自定义视图控制器呈现时的容器(允许您正确添加自己的手势识别器)。
这里还有一个非常简单易懂的教程链接:http://dativestudios.com/blog/2014/06/29/presentation-controllers/ 然后添加暗化视图点击以解除:
    UITapGestureRecognizer *singleFingerTap = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(handleSingleTap:)];
    [self.dimmingView addGestureRecognizer:singleFingerTap];


- (void)handleSingleTap:(UITapGestureRecognizer *)recognizer {
    [self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}

1
我猜测这个教程被踩是因为它只讲了如何动画过渡,而没有提到一个点击选项卡外部进行关闭的功能。不清楚UIPresentationController类是否有任何解决此问题的方法(未阅读文档)。 - webjprgm
1
由于UIPresentationController是正确的方法,我更新了答案以包括所需的点击以关闭代码示例。而且,苹果的示例代码非常好,而不是那篇博客文章:https://developer.apple.com/library/ios/featuredarticles/ViewControllerPGforiPhoneOS/DefiningCustomPresentations.html#//apple_ref/doc/uid/TP40007457-CH25-SW1 - malhal
我个人更喜欢这种方法,因为我曾经遇到过这样的情况:一个表单表格呈现了一个弹出视图,而该弹出视图可能会超出所呈现的表单表格的边界 - 如果您在表单表格范围之外的任何地方点击弹出视图,基于view.window的手势识别器将关闭该视图。我更希望暗化视图处理这个点击事件! - Ben Kreeger
在使用自定义模态Segue和UIPresentationController时,在iOS 12中仍然是最好的方法。不要忘记添加“self.dimmingView setUserInteractionEnabled:YES”,否则手势识别器将无法捕获任何东西。 - Neimsz

7

这是一个适用于竖屏和横屏的Swift 3.1解决方案。

class TapBehindModalViewController: UIViewController, UIGestureRecognizerDelegate {
    private var tapOutsideRecognizer: UITapGestureRecognizer!

    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)

        if(self.tapOutsideRecognizer == nil) {
            self.tapOutsideRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTapBehind))
            self.tapOutsideRecognizer.numberOfTapsRequired = 1
            self.tapOutsideRecognizer.cancelsTouchesInView = false
            self.tapOutsideRecognizer.delegate = self
            self.view.window?.addGestureRecognizer(self.tapOutsideRecognizer)
        }
    }

    override func viewWillDisappear(_ animated: Bool) {
        super.viewWillDisappear(animated)

        if(self.tapOutsideRecognizer != nil) {
            self.view.window?.removeGestureRecognizer(self.tapOutsideRecognizer)
            self.tapOutsideRecognizer = nil
        }
    }

    func close(sender: AnyObject) {
        self.dismiss(animated: true, completion: nil)
    }

    // MARK: - Gesture methods to dismiss this with tap outside
    func handleTapBehind(sender: UITapGestureRecognizer) {
        if (sender.state == UIGestureRecognizerState.ended) {
            let location: CGPoint = sender.location(in: self.view)

            if (!self.view.point(inside: location, with: nil)) {
                self.view.window?.removeGestureRecognizer(sender)
                self.close(sender: sender)
            }
        }
    }

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
        return true
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接