Reputation: 1
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)
}
}
Upvotes: 0
Views: 2640
Reputation: 6606
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!
Upvotes: 0
Reputation: 2201
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 😃.
Upvotes: 0
Reputation: 2201
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)
}
}
Upvotes: 0
Reputation: 559
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)
}
Upvotes: 0
Reputation: 73
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.
Upvotes: 0