如何在自定义转场动画执行中途中断并关闭视图控制器?
解决自定义转场中点击调光视图取消转场的问题
核心问题分析
当前代码的两个关键问题:
UIDimmingView的hitTest方法返回nil,导致点击调光视图本身时无法触发手势识别- 转场过程中直接调用
dismiss会和正在进行的转场动画冲突,需要先中断当前动画再完成关闭操作
步骤1:修复调光视图的点击响应
修改UIDimmingView的hitTest方法,让它能响应自身的点击:
class UIDimmingView: UIView { override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { let hitView = super.hitTest(point, with: event) // 点击自身时返回self,确保手势能捕捉到点击事件 return hitView == self ? self : hitView } }
步骤2:将手势绑定到调光视图
把点击手势直接添加到dimmingView上,而非容器视图,确保只有点击调光区域才触发取消逻辑:
lazy var tapGesture: UITapGestureRecognizer = { let gesture = UITapGestureRecognizer(target: self, action: #selector(onTap(_:))) return gesture }() lazy var dimmingView: UIDimmingView = { let view = UIDimmingView() view.backgroundColor = .black.withAlphaComponent(0.4) view.alpha = 0.0 view.addGestureRecognizer(self.tapGesture) // 绑定手势到调光视图 return view }()
步骤3:同步转场动画并支持中断
在转场开始时,使用转场协调器同步调光视图和弹窗的动画;同时在自定义转场动画控制器中添加取消标记,处理动画中断逻辑:
更新转场准备方法
override func presentationTransitionWillBegin() { guard let containerView, let presentedView, let coordinator = presentingViewController.transitionCoordinator else { return } containerView.insertSubview(self.dimmingView, at: 0) self.dimmingView.fillSuperview() presentedView.layer.cornerRadius = 20.0 presentedView.layer.cornerCurve = .continuous presentedView.layer.masksToBounds = true // 用转场协调器同步调光视图的淡入动画 coordinator.animate(alongsideTransition: { _ in self.dimmingView.alpha = 1.0 }, completion: nil) }
自定义转场动画控制器(添加取消逻辑)
class CustomPresentationAnimator: NSObject, UIViewControllerAnimatedTransitioning { var shouldCancelTransition = false weak var dimmingView: UIDimmingView? // 弱引用调光视图 func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.3 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let toVC = transitionContext.viewController(forKey: .to), let toView = transitionContext.view(forKey: .to) else { return } let containerView = transitionContext.containerView containerView.addSubview(toView) // 初始状态:弹窗缩小+透明 toView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) toView.alpha = 0 UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveEaseInOut) { if self.shouldCancelTransition { // 取消转场:回到初始透明缩小状态 toView.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) toView.alpha = 0 self.dimmingView?.alpha = 0 } else { // 正常转场:恢复弹窗到正常状态 toView.transform = .identity toView.alpha = 1 } } completion: { [weak self] finished in guard let self = self else { return } if self.shouldCancelTransition { // 标记转场失败,然后关闭弹窗 transitionContext.completeTransition(false) toVC.dismiss(animated: false) } else { transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } // 重置取消标记 self.shouldCancelTransition = false } } }
步骤4:处理点击事件
在点击回调中设置取消标记,触发动画中断:
@objc private func onTap(_ sender: UITapGestureRecognizer) { // 假设animator是你的自定义转场动画控制器实例 self.animator.shouldCancelTransition = true // 强制触发动画更新 UIView.animate(withDuration: 0.2) { self.dimmingView.alpha = 0 self.presentedViewController.view.transform = CGAffineTransform(scaleX: 0.9, y: 0.9) self.presentedViewController.view.alpha = 0 } completion: { _ in self.presentedViewController.dismiss(animated: false) } }
内容的提问来源于stack exchange,提问作者Murad
相关产品推荐
相关产品推荐

