如何将点击手势添加到变暗的视图背景?

How to add tap gesture to a dimmed view background?

提问人:Marik 提问时间:5/16/2020 更新时间:6/14/2021 访问量:2062

问:

我已经尝试了一段时间。下面的代码是我的 UIPresentationController。当按下一个按钮时,我添加了一个变暗的 UIView,第二个模式 (presentedViewController) 在中途弹出。

我在方法presentationTransitionWillBegin()中添加了点击手势识别器 我不知道为什么当我单击变暗的 UIView 时没有注册点击手势。

我尝试更改“目标”并在不同的地方添加手势。还查看了其他帖子,但没有任何效果。

谢谢

import UIKit

class PanModalPresentationController: UIPresentationController {

    override var frameOfPresentedViewInContainerView: CGRect {
        var frame: CGRect = .zero
        frame.size = size(forChildContentContainer: presentedViewController, withParentContainerSize: containerView!.bounds.size)
        frame.origin.y = containerView!.frame.height * (1.0 / 2.0)
        print("frameOfPresentedViewInContainerView")
        return frame
    }

    private lazy var dimView: UIView! = {
        print("dimView")
        guard let container = containerView else { return nil }

        let dimmedView = UIView(frame: container.bounds)
        dimmedView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
        dimmedView.isUserInteractionEnabled = true

        return dimmedView
    }()

    override init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?) {
        print("init presentation controller")
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
    }

    override func presentationTransitionWillBegin() {

        guard let container = containerView else { return }
        print("presentation transition will begin")

        container.addSubview(dimView)
        dimView.translatesAutoresizingMaskIntoConstraints = false
        dimView.topAnchor.constraint(equalTo: container.topAnchor).isActive = true
        dimView.leadingAnchor.constraint(equalTo: container.leadingAnchor).isActive = true
        dimView.trailingAnchor.constraint(equalTo: container.trailingAnchor).isActive = true
        dimView.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
        dimView.isUserInteractionEnabled = true

        let recognizer = UITapGestureRecognizer(target: self, action: #selector(self.handleTap(_:)))
        dimView.addGestureRecognizer(recognizer)

        container.addSubview(presentedViewController.view)
        presentedViewController.view.translatesAutoresizingMaskIntoConstraints = false
        presentedViewController.view.bottomAnchor.constraint(equalTo: container.bottomAnchor).isActive = true
        presentedViewController.view.widthAnchor.constraint(equalTo: container.widthAnchor).isActive = true
        presentedViewController.view.heightAnchor.constraint(equalTo: container.heightAnchor).isActive = true

        guard let coordinator = presentingViewController.transitionCoordinator else { return }
        coordinator.animate(alongsideTransition: { _ in
            self.dimView.alpha = 1.0
        })

        print(dimView.alpha)
    }

    override func dismissalTransitionWillBegin() {
        guard let coordinator = presentedViewController.transitionCoordinator else {
            print("dismissal coordinator")
            self.dimView.alpha = 0.0
            return
        }
        print("dismissal transition begin")
        coordinator.animate(alongsideTransition: { _ in
            self.dimView.alpha = 0.0
        })
    }

    override func containerViewDidLayoutSubviews() {
        print("containerViewDidLayoutSubviews")
        presentedView?.frame = frameOfPresentedViewInContainerView
//        presentedViewController.dismiss(animated: true, completion: nil)
    }

    override func size(forChildContentContainer container: UIContentContainer, withParentContainerSize parentSize: CGSize) -> CGSize {
        print("size")
        return CGSize(width: parentSize.width, height: parentSize.height * (1.0 / 2.0))
    }

    @objc func handleTap(_ sender: UITapGestureRecognizer) {
        print("tapped")
        //        presentingViewController.dismiss(animated: true, completion: nil)
        presentedViewController.dismiss(animated: true, completion: nil)
    }
}
iOS Swift UIGogestureRecognizer UIPopOverPresentationController

评论

0赞 Peter Parker 5/16/2020
在 frameOfPresentedView 中,使用 sizeForChild 将呈现的视图设置为容器视图大小的一半,并将其放置在屏幕的下半部分。但是在 presentationWillBegin 中,您将对呈现的视图进行约束,以使其与容器的整个大小相匹配。那里有不匹配。也许这导致了问题?你需要这些约束吗?
0赞 Marik 5/16/2020
我删除了这些约束,但变暗的视图仍然没有响应点击手势

答:

0赞 Luke Allen 5/16/2020 #1

我无法判断您的 presentedViewController.view 的帧/边界是什么,但即使它的上半部分的 alpha 为 0,它也可能覆盖您的 dimView 并接收点击事件而不是 dimView - 因为 presentedViewController.view 被添加为 dimView 顶部的子视图。

0赞 rhpekarek 5/16/2020 #2

您可能需要等到控制器显示后,将手势添加到其超级视图的第一个子视图中。我以前曾使用它来关闭带有背景点击的自定义警报控制器。你可以做类似的事情:

viewController.present(alertController, animated: true) {
     // Enabling Interaction for Transparent Full Screen Overlay
     alertController.view.superview?.subviews.first?.isUserInteractionEnabled = true
     let tapGesture = UITapGestureRecognizer(target: alertController, action: #selector(alertController.dismissSelf))
     alertController.view.superview?.subviews.first?.addGestureRecognizer(tapGesture)
}
0赞 Peter Parker 5/16/2020 #3

嗯,试着用它来代替。让我知道它是怎么回事。它对我有用。

class PC: UIPresentationController {

    /*
     We'll have a dimming view behind.
     We want to be able to tap anywhere on the dimming view to do a dismissal.
     */

    override var frameOfPresentedViewInContainerView: CGRect {

        let f = super.frameOfPresentedViewInContainerView
        var new = f
        new.size.height /= 2
        new.origin.y = f.midY
        return new
    }

    override func presentationTransitionWillBegin() {

        let con = self.containerView!
        let v = UIView(frame: con.bounds)
        v.backgroundColor = UIColor.black
        v.alpha = 0
        con.insertSubview(v, at: 0)

        let tap = UITapGestureRecognizer(target: self, action: #selector(handleTap))
        v.addGestureRecognizer(tap)

        let tc = self.presentedViewController.transitionCoordinator!
        tc.animate(alongsideTransition: { _ in
            v.alpha = 1
        }, completion: nil)
    }

    @objc func handleTap() {
        print("tapped")
        self.presentedViewController.dismiss(animated: true, completion: nil)
    }

    override func dismissalTransitionWillBegin() {

        let con = self.containerView!
        let v = con.subviews[0]

        let tc = self.presentedViewController.transitionCoordinator!
        tc.animate(alongsideTransition: { _ in
            v.alpha = 0
        }, completion: nil)

    }
}

评论

0赞 Marik 5/16/2020
谢谢,但它不起作用哈哈。我复制粘贴了你的代码。视野变暗。然后我点击视图,什么也没发生。视图不会撤消暗淡。我正在使用 Swift 4 和 Xcode 9。
0赞 Peter Parker 5/16/2020
🤪 哈哈。好吧,你甚至把“敲击”打印出来了吗?此外,在该 PC 类中添加一个带有一些 print 语句的 deinit。也许 PC 实例在演示后立即取消初始化?我正在使用 Swift 5 和 Xcode 11.3
0赞 Marik 5/16/2020
没有“敲击”打印出来的哈哈!是的,我会尝试添加 deinit。谢谢!
0赞 Peter Parker 5/16/2020
哈哈,不客气。我们会弄清楚的。最终。
0赞 Marik 5/16/2020
嗯。.没什么哈哈..没有“敲击”打印出来。也没有打印出 deinit 消息。还有其他想法吗?
0赞 Peter Parker 5/16/2020 #4

我刚才看了一下你的项目。问题出在动画控制器上。如果注释掉过渡委托对象中用于提供动画控制器的函数,则一切正常。

但只要看看你的动画控制器,你想要实现的就是让你的新 vc 向上/向下滑动。事实上,您甚至不需要自定义动画控制器;视图控制器的属性的默认值为 ,我认为这正是您想要的。modalTransitionStylecoverVertical

无论如何,您仍然可以使用我之前发布的演示控制器类,因为它与您的类具有相同的语义,只是没有不必要的覆盖。

自选

另外,如果您愿意,也只是一个提示,您现在在项目中拥有这些文件:

PanModalPresentationDelegate.swift
PanModalPresentationController.swift
PanModalPresentationAnimator.swift
TaskViewController.swift
HomeViewController.swift

我通常做的是缩写一些长短语,以便文件和类的名称传达其本质的本质,而无需冗长的不需要的样板。

所以 和 将是 和 .其他 3 个文件都用于演示一个 VC;它可能很快就会失控。因此,我通常在那里做的是调用我的演示控制器并将其声明嵌套在将使用它的 VC 类中(在本例中是)。直到它也需要被其他一些 VC 使用的时候到来;然后把它放在它自己的文件中并调用它更合适,但我实际上从来没有真正需要这样做,哈哈。任何动画控制器也是如此,例如等。我倾向于将过渡委托称为 a,并将其嵌套在所呈现的 VC 的类中。让我更容易把它看作是出售 AC/PC 的东西。HomeViewControllerTaskViewControllerHome_VCTask_VCPCTask_VCSomething_PCFade_ACSlide_ACTransitionManager

那么你的项目就变成了:

Home_VC.swift
Task_VC.swift

如果你进去,你会看到一个嵌套的 和 .Task_VCTransitionManagerPC

但是,是的,这取决于你😃.

0赞 Sheamus 6/14/2021 #5

dimmedView 位于呈现视图的后面。您有几个选项可以纠正此问题。

首先,是允许触摸通过顶视图,它必须覆盖 pointInside:

- (BOOL) pointInside:(CGPoint)point withEvent:(UIEvent *)event {
    for (UIView *subview in self.subviews) {
        if ([subview hitTest:[self convertPoint:point toView:subview] withEvent:event]) {
            return TRUE;
        }
    }

    return FALSE;
}

另一种选择是将手势识别器添加到 presentedViewController.view,而不是 dimmedView。而且,如果允许 PanModalPresentationController 采用 UIGestureRecognizerDelegate,并将其作为识别器的委托,则可以通过实现 shouldReceive 触摸来确定是否应响应触摸:

    func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldReceive touch: UITouch) -> Bool {
        if (touch.view == presentedViewController.view) {
            return true
        }
    
        return false
    }

如果使用第二个选项,请不要忘记删除 dismissalTransitionWillBegin 或 dismissalTransitionDidEnd 中的手势识别器!