I have a CustomPresentationController
which animates in and out with custom animations;
This specific controller gets presented, more less at 50% of the screen size, and when I present it, I add a shadow-gray view to the presentingViewController
so it adds some depth.
I can only dismiss the presentedViewController
if I tap the cancel
button in the NavBar
which I call the default dismiss(:)
method.
What I'm trying to accomplish is to detect a tap outside the presentedViewController
, maybe inside the gray zone, so I can dismiss the presentedViewController
, somehow like dismissing an ActionSheet
but I've failed to do it. Let me explain what I've tried so far.
I tried to add a UITapGestureRecognizer
to the shadow-gray view but since I'm presenting a different controller, the app-engine might think that since the shadow view isn't on the top hierarchy view it might not be accessible so it 'blocks' the recognizer - whenever I tap it, the gesture handles doesn't fire.
I'm implementing now in addition a swipe down to dismiss, which I can make it easily, but I really wanted the tap-outside feature to work as well.
Any hint on how can I approach this?
The apps image is the following:
My solution:
In presenting view controller (aka ViewControllerA):
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let vcb = storyboard.instantiateViewController(withIdentifier: "ViewControllerB") as! ViewControllerB // ViewControllerB is the presented view controller
vcb.modalPresentationStyle = .custom
vcb.transitioningDelegate = self
modalRatio = Float(0.5) // modalRatio is an object property
self.present(pvc, animated: true)
ViewControllerA shall also implement Transitioning delegate:
extension ViewControllerA: UIViewControllerTransitioningDelegate {
func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
return PartialSizePresentController(presentedViewController: presented, presenting: presenting, withRatio: modalRatio ?? 0.5) // modal ratio is configurable using modalRatio property
}
}
Then, implement the presentation controller (aka PartialSizePresentController), so that it also handles tap gesture:
class PartialSizePresentController: UIPresentationController {
let heightRatio : CGFloat
init(presentedViewController: UIViewController, presenting presentingViewController: UIViewController?, withRatio ratio: Float = 0.5) {
heightRatio = CGFloat(ratio)
super.init(presentedViewController: presentedViewController, presenting: presentingViewController)
}
override var frameOfPresentedViewInContainerView: CGRect {
guard let cv = containerView else { fatalError("No container view available") }
return CGRect(x: 0, y: cv.bounds.height * (1 - heightRatio), width: cv.bounds.width, height: cv.bounds.height * heightRatio)
}
override func presentationTransitionWillBegin() {
let bdView = UIView(frame: containerView!.bounds)
bdView.backgroundColor = UIColor.black.withAlphaComponent(0.5)
containerView?.addSubview(bdView)
bdView.addSubview(presentedView!)
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(PartialSizePresentController.handleTap(_:)))
bdView.addGestureRecognizer(tapGesture)
}
@objc func handleTap(_ sender: UITapGestureRecognizer) {
presentedViewController.dismiss(animated: true, completion: nil)
}
}