简体   繁体   中英

How to add tap gesture to a dimmed view background?

I've been trying this for awhile. The code below is my UIPresentationController. When a button is pressed, I add a dimmed UIView and a second modal (presentedViewController) pops up halfway.

I added the tap gesture recognizer in the method presentationTransitionWillBegin() I don't know why the tap gesture is not being registered when I click on the dimmed UIView.

I've tried changing the "target" and adding the gesture in a different place. Also looked at other posts, but nothing has worked for me.

Thanks

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

I can't tell what the frame/bounds of your presentedViewController.view is but even if it's top half has an alpha of 0 it could be covering your dimView and receiving the tap events instead of the dimView - since presentedViewController.view is added as a subview on top of dimView.

You may have to wait until after the controller is presented and add the gesture to its superview's first subview. I've used this before to dismiss a custom alert controller with a background tap. You could probably do something similar:

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

Hmm, try using this instead. Let me know how it goes. It works for me.

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)

    }
}

I took a look at your project just now. The problem is in your animation controller. If you comment out the functions in your transition delegate object that vend animation controllers, everything works fine.

But just looking at your animation controller, what you wanted to achieve was to have your new vc slide up / slide down. And in fact, you don't even need a custom animation controller for this; the modalTransitionStyle property of a view controller has a default value of coverVertical , which is just what you want I think.

In any case though, you can still use the presentation controller class I posted before, as it has same semantics from your class, just without unnecessary overrides.

Optional

Also just a tip if you'd like, you have these files right now in your project:

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

What I normally do is abbreviate some of those long phrases, so that the name of the file and class conveys the essence of its nature without long un-needed boilerplate.

So HomeViewController and TaskViewController would be Home_VC and Task_VC . Those other 3 files are all for the presentation of one VC; it can get out of hand very quickly. So what I normally do there is call my presentation controller just PC and nest its declaration inside the VC class that will use it (in this case that's Task_VC ). Until the time comes where it needs to be used by some other VC too; then it's more appropriate to put it in its own file and call it Something_PC but I've never actually needed to do that yet lol. And the same for any animation controllers ex. Fade_AC , Slide_AC etc. I tend to call transition delegate a TransitionManager and nest it in the presented VC's class. Makes it easier for me to think of it as just a thing that vends AC's / a PC.

Then your project simply becomes:

Home_VC.swift
Task_VC.swift

And if you go inside Task_VC , you'll see a nested TransitionManager and PC .

But yeah up to you.

The dimmedView is behind presented view. You have a couple options to correct that.

First, is allow touches to pass through the top view, it must override 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;
}

Another options is to instead add the gesture recognizer to the presentedViewController.view, instead of the dimmedView. And, if you allow PanModalPresentationController to adopt the UIGestureRecognizerDelegate, and it as the delegate to the recognizer, you can determine if you should respond to touches, by implementing shouldReceive touch:

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

If you use the second option, don't forget to remove the gesture recognizer in dismissalTransitionWillBegin or dismissalTransitionDidEnd!

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM