iOS开发:如何使presentableView外部的dismissButton可点击?
问题描述
我创建了一个自定义UIPresentationController,希望屏幕顶部1/6区域包含dismissButton和dimmingView,分别在弹窗展示和消失时淡入淡出。但由于dismissButton位于presentedView外部,无法被点击。请问有解决方案吗?
注:我知道可以将子视图添加到presentedView后再把dismissButton移到视图外,但这样按钮会和控制器其他部分一样以向上滑动的方式弹出,而非我想要的淡入效果。
相关代码
@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() }) } }
解决方案
核心问题是你把dismissButton添加到了presentingViewController.view上,弹窗展示时presentedViewController.view的窗口层级更高,拦截了点击事件。这里提供两个可行方案:
方案一:将按钮和蒙层添加到容器视图(Container View)
UIPresentationController自带的containerView是专门承载弹窗相关视图的容器,层级关系更合理,推荐使用这个方案:
- 修改
presentationTransitionWillBegin方法,将视图添加到containerView而非原控制器视图:
open override func presentationTransitionWillBegin() { super.presentationTransitionWillBegin() guard let containerView = containerView else { return } // 把视图添加到containerView containerView.addSubview(dimmingView) containerView.addSubview(dismissButton) // 约束目标换成containerView NSLayoutConstraint.activate([ dimmingView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), dimmingView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), dimmingView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor), dimmingView.topAnchor.constraint(equalTo: containerView.topAnchor), dismissButton.topAnchor.constraint(equalTo: containerView.topAnchor, constant: .marginFromTopOfScreen), dismissButton.leftAnchor.constraint(equalTo: containerView.leftAnchor, constant: .marginLeft), dismissButton.heightAnchor.constraint(equalToConstant: iconSize), dismissButton.widthAnchor.constraint(equalToConstant: iconSize), ]) dismissButton.layer.cornerRadius = iconSize/2 // 给按钮直接添加点击事件,替代原来的蒙层手势判断 dismissButton.addTarget(self, action: #selector(dismissButtonTapped), for: .touchUpInside) dimmingView.alpha = 0 dismissButton.alpha = 0 presentingViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in self?.dimmingView.alpha = 1 // 同时给按钮添加淡入动画 self?.dismissButton.alpha = 1 }, completion: nil) }
- 添加按钮点击的响应方法:
@objc private func dismissButtonTapped() { presentedViewController.dismiss(animated: true) }
- 调整消失动画,给按钮也加上淡出效果:
open override func dismissalTransitionWillBegin() { super.dismissalTransitionWillBegin() presentingViewController.transitionCoordinator?.animate(alongsideTransition: { [weak self] _ in self?.dimmingView.alpha = 0 self?.dismissButton.alpha = 0 }, completion: { [weak self] _ in self?.dimmingView.removeFromSuperview() self?.dismissButton.removeFromSuperview() }) }
这样按钮和蒙层都在containerView中管理,层级不会被弹窗遮挡,能正常响应点击,同时淡入淡出的动画效果也能独立实现。
方案二:手动提升按钮层级
如果坚持要把按钮放在原控制器视图上,可以在弹窗展示完成后将按钮提到最顶层:
open override func presentationTransitionDidEnd(_ completed: Bool) { super.presentationTransitionDidEnd(completed) if completed { presentingViewController.view.bringSubviewToFront(dismissButton) } }
但这种方式容易造成视图层级混乱,不如方案一符合UIPresentationController的设计规范,不推荐优先使用。
内容的提问来源于stack exchange,提问作者helloworld12345
相关产品推荐
相关产品推荐

