[英]iOS - Dismiss presented view controller touching outside its View
I have a CustomPresentationController
which animates in and out with custom animations;我有一个CustomPresentationController
,它使用自定义动画进出动画;
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.这个特定的控制器被呈现,在屏幕尺寸的 50% 处更少,当我呈现它时,我向presentingViewController
添加了一个阴影灰色视图,因此它增加了一些深度。
I can only dismiss the presentedViewController
if I tap the cancel
button in the NavBar
which I call the default dismiss(:)
method.如果我点击NavBar
的cancel
按钮,我只能关闭presentedViewController
,我调用默认的dismiss(:)
方法。
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.我想要完成的是检测presentedViewController
之外的点击,可能在灰色区域内,这样我就可以关闭presentedViewController
,就像关闭ActionSheet
但我没有做到。 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.我试图将一个UITapGestureRecognizer
添加到阴影灰色视图中,但由于我展示了一个不同的控制器,应用引擎可能认为由于阴影视图不在顶层视图中,所以它可能无法访问,因此它“阻塞” ' 识别器 - 每当我点击它时,手势句柄都不会触发。
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.我现在正在实施另外一个向下滑动以关闭,我可以很容易地做到这一点,但我真的希望tap-outside功能也能正常工作。
Any hint on how can I approach this?关于如何解决这个问题的任何提示?
The apps image is the following:应用程序图像如下:
My solution:我的解决方案:
In presenting view controller (aka ViewControllerA):在呈现视图控制器(又名 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: ViewControllerA 还应实现转换委托:
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:然后,实现呈现控制器(又名 PartialSizePresentController),以便它也处理点击手势:
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)
}
}
Try with my below code:尝试使用我的以下代码:
You need to implement this method inside you presented controller which you are working with as a popup.您需要在您作为弹出窗口使用的控制器中实现此方法。
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
// Here write down you logic to dismiss controller
}
Hope this will work.希望这会奏效。 :D :D
I just had to implement this in one of my app.我只需要在我的一个应用程序中实现它。
I made it worked by adding a button that covers the entire view and this button, once tapped triggers the VC to be dismissed.我通过添加一个覆盖整个视图的按钮来实现它,这个按钮一旦被点击就会触发 VC 被关闭。
Once the button is added you can add your custom View on top.添加按钮后,您可以在顶部添加自定义视图。
So far it looks like it's working pretty well.到目前为止,它看起来运行良好。
My code below (I do everything programmatically, no storyboard)我的代码如下(我以编程方式执行所有操作,没有故事板)
//—————————————————————————————
// MARK: View Life Cycle
//—————————————————————————————
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.clear //VC view background transparent
setupUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
//Animate blackView opacity to 1 to give some depth
UIView.animate(withDuration: 0.4, delay: 0.2, options: .curveEaseInOut, animations: {
self.blackView.alpha = 1
})
}
//————————————————
// MARK: Setup UI
//————————————————
let blackView: UIView = {
let view = UIView()
view.alpha = 0.0
view.backgroundColor = UIColor.black.withAlphaComponent(0.6)
return view
}()
//Invisible button which covers the entire view that can be tapped
lazy var dismissLayerBtn: UIButton = {
let btn = UIButton()
btn.addTarget(self, action: #selector(tapToDismiss), for: .touchUpInside)
return btn
}()
@objc func tapToDismiss() {
print("tapToDimiss")
self.dismiss(animated: true, completion: nil)
}
let milestonePickerView: MilestonePickerView = {
let view = MilestonePickerView(frame: .zero)
return view
}()
func setupUI() {
view.addSubview(blackView)
view.addSubview(dismissLayerBtn)
view.addSubview(milestonePickerView) //Important to add the customView after the button.
blackView.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
dismissLayerBtn.anchor(top: view.topAnchor, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 0, paddingBottom: 0, paddingRight: 0, width: 0, height: 0)
milestonePickerView.anchor(top: nil, left: view.leftAnchor, bottom: view.bottomAnchor, right: view.rightAnchor, paddingTop: 0, paddingLeft: 20, paddingBottom: 40, paddingRight: 20, width: 0, height: 400)
//I'm using a custom extension to setup constraints (anchors)
}
If you're using storyboard, make sure you put the invisible button under the custom view.如果您使用故事板,请确保将不可见按钮放在自定义视图下。
I hope this helps.我希望这会有所帮助。
// it works for me // 这个对我有用
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
var touch: UITouch? = touches.first
if touch?.view == yourView {
navigationController?.popViewController(animated: true)
dismiss(animated: true, completion: nil)
}
}
You were on the right track with the UITapGestureRecognizer
.您使用UITapGestureRecognizer
在正确的轨道上。 Just make sure you implement the shouldRecognizeSimultaneouslyWith
as such:只要确保你实现shouldRecognizeSimultaneouslyWith
如下:
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true
}
This should allow the gesture to fire correctly.这应该允许手势正确触发。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.