1

I created a custom UIPresentationController and want the top sixth of the screen to contain a dismissButton and dimmingView to fade in and out on present and dismiss, respectively. However, because the dismissButton is outside the presentedView, it is not tappable. Is there any solution to this?

Note: I know I can add the subview to the presentedView and then move the dismissButton outside of the view, but if I do this, the button presents in the same way that the rest of the controller does, instead of fading in like I want it to (not sliding up).

@available(iOS 13.0, *)
open class CUICFiveSixthPopUpTransitioningDelegate: NSObject, UIViewControllerTransitioningDelegate {
    
    let dimmingColor: UIColor!
    
    public init(dimmingColor: UIColor) {
        self.dimmingColor = dimmingColor
        super.init()
    }
    
    public func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
        return CUICFiveSixthPopUpPresentationController(presentedViewController: presented, presenting: presenting, dimmingColor: dimmingColor)
    }
}

@available(iOS 13.0, *)
open class CUICFiveSixthPopUpPresentationController: UIPresentationController {
    open override var frameOfPresentedViewInContainerView: CGRect {
        let bounds = presentingViewController.view.bounds
        
        let size = CGSize(width: UIScreen.main.bounds.width, height: UIScreen.main.bounds.height*5/6)
        let origin = CGPoint(x: bounds.midX - size.width / 2, y: UIScreen.main.bounds.height - size.height)
        return CGRect(origin: origin, size: size)
    }
    
    public init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, dimmingColor: UIColor) {
        super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
        
        presentedView?.autoresizingMask = [
            .flexibleTopMargin,
            .flexibleBottomMargin,
            .flexibleLeftMargin,
            .flexibleRightMargin
        ]
        
        presentedViewController.view.layer.cornerRadius = viewRadius
        presentedViewController.view.clipsToBounds = true
        dimmingView.backgroundColor = dimmingColor
        dismissButton.backgroundColor = dimmingColor.lighter()
        
        presentedView?.translatesAutoresizingMaskIntoConstraints = true
    }
    
    let iconSize: CGFloat = 40
    let viewRadius: CGFloat = 35
    
    let dimmingView: UIView = {
        let dimmingView = UIView(frame: .zero)
        dimmingView.translatesAutoresizingMaskIntoConstraints = false
        return dimmingView
    }()
    
    let dismissButton: UIButton = {
        let dismissButton = UIButton(frame: .zero)
        dismissButton.tintColor = .white
        dismissButton.alpha = 0.8
        dismissButton.setImage(.close, for: .normal)
        dismissButton.translatesAutoresizingMaskIntoConstraints = false
        return dismissButton
    }()
    
    @objc private func dimmingViewTapped(_ sender: UITapGestureRecognizer) {
        let point = sender.location(in: presentingViewController.view!)
        if dismissButton.frame.contains(point) {
            presentedViewController.dismiss(animated: true)
        }
    }
    
    open override func presentationTransitionWillBegin() {
        super.presentationTransitionWillBegin()
        
        let superview = presentingViewController.view!
        let presentedView = presentedView!
        
        superview.addSubview(dimmingView)
        superview.addSubview(dismissButton)
        
        NSLayoutConstraint.activate([
            dimmingView.leadingAnchor.constraint(equalTo: superview.leadingAnchor),
            dimmingView.trailingAnchor.constraint(equalTo: superview.trailingAnchor),
            dimmingView.bottomAnchor.constraint(equalTo: superview.bottomAnchor),
            dimmingView.topAnchor.constraint(equalTo: superview.topAnchor),
            
            dismissButton.topAnchor.constraint(equalTo: superview.topAnchor, constant: .marginFromTopOfScreen),
            dismissButton.leftAnchor.constraint(equalTo: superview.leftAnchor, constant: .marginLeft),
            dismissButton.heightAnchor.constraint(equalToConstant: iconSize),
            dismissButton.widthAnchor.constraint(equalToConstant: iconSize),
        ])
        dismissButton.layer.cornerRadius = iconSize/2
        let tapGesture = UITapGestureRecognizer(target: self, action: #selector(dimmingViewTapped(_:)))
        dimmingView.addGestureRecognizer(tapGesture)
        
        dimmingView.alpha = 0
        presentingViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in
            self?.dimmingView.alpha = 1
        }, completion: nil)
    }
    
    open override func dismissalTransitionWillBegin() {
        super.dismissalTransitionWillBegin()
        
        presentingViewController.transitionCoordinator?.animate(alongsideTransition: { _ in
            self.dimmingView.alpha = 0
        }, completion: { _ in
            self.dimmingView.removeFromSuperview()
        })
    }
}
HangarRash
  • 7,314
  • 5
  • 5
  • 32
helloworld12345
  • 176
  • 1
  • 4
  • 22
  • 1
    Add the button into `containerView` and not in `presentedView`, then it should not slide. You should do it in `presentationTransitionWillBegin`. Fade it in alongside animations if you like. – pronebird Feb 25 '23 at 08:10
  • You can use `hitTest(...)` to manage touches outside of a view's bounds... Difficult to tell you exactly what to do with only the code you've posted. Can you add enough code for a [mre]? – DonMag Feb 25 '23 at 14:02
  • 1
    @pronebird thank you! Adding these to the containerView fixed my issue! – helloworld12345 Feb 25 '23 at 19:31

1 Answers1

1

Thanks to a comment above, I figured out the issue. You cannot recognize gestures when the UIElements are added to the superview; however, you can access these gestures when the are added to the containerView.

Example:

let superview = presentingViewController.view!
let presentedView = presentedView!
let containerView = containerView!
        
superview.addSubview(dimmingView)
containerView.addSubview(dismissButton) // this was changed from superView
helloworld12345
  • 176
  • 1
  • 4
  • 22