Swift4无Storyboard/导航控制器时,如何无异常动画dismiss两个模态控制器
Swift 4:从模态链C直接回到A且动画正常的解决方案
我完全理解你遇到的这个动画问题——用链式调用self.presentingViewController?.presentingViewController?.dismiss()确实能实现跳转,但动画会出现C直接消失、只有B执行退场动画的突兀效果,这是因为系统默认只会对直接被dismiss的控制器(也就是B)执行动画,而C作为B的子模态控制器会被直接移除。下面给你两个靠谱的解决方案,都能实现平滑的从C回到A的动画:
方案一:连续dismiss并衔接动画(简单易实现)
这个方案是先让C退场到B,再在C退场动画完成后让B退场到A,两个动画连贯执行,视觉上不会有突兀感:
// 在ViewController C中的返回按钮或触发事件里添加这段代码 self.dismiss(animated: true) { // 等C的退场动画完成后,让B也退场到A self.presentingViewController?.dismiss(animated: true, completion: nil) }
这种方法的优势是不需要额外配置,完全用系统默认动画,只是把两个dismiss操作通过completion回调衔接起来,用户看到的是连续的退场动画,不会出现控件突然消失的问题。
方案二:自定义转场动画(实现直接从C到A的单步动画)
如果你希望实现直接从C跳回A的单步退场动画(不需要经过B的过渡),可以自定义转场动画。步骤如下:
- 首先在ViewController C中遵守
UIViewControllerTransitioningDelegate协议:
class ViewControllerC: UIViewController, UIViewControllerTransitioningDelegate { override func viewDidLoad() { super.viewDidLoad() self.transitioningDelegate = self } }
- 实现自定义的退场动画控制器(这里模拟系统从下到上present的反向动画,即从上到下退场):
class CustomDismissAnimator: NSObject, UIViewControllerAnimatedTransitioning { func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.35 // 和系统默认动画时长一致 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { guard let fromVC = transitionContext.viewController(forKey: .from), let toVC = transitionContext.viewController(forKey: .to) else { transitionContext.completeTransition(false) return } let containerView = transitionContext.containerView containerView.insertSubview(toVC.view, belowSubview: fromVC.view) // 自定义退场动画:让C的视图从上滑出屏幕 let screenHeight = UIScreen.main.bounds.height UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: { fromVC.view.frame.origin.y = screenHeight }) { _ in transitionContext.completeTransition(!transitionContext.transitionWasCancelled) } } }
- 在ViewController C中实现转场代理方法,返回自定义动画控制器:
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { return CustomDismissAnimator() }
- 最后,在触发返回的事件中,直接获取到A并调用dismiss:
// 递归找到最底层的presenting控制器(也就是A) func rootPresentingViewController() -> UIViewController? { var currentVC = self.presentingViewController while let nextVC = currentVC?.presentingViewController { currentVC = nextVC } return currentVC } // 触发返回的方法 func goBackToA() { rootPresentingViewController()?.dismiss(animated: true, completion: nil) }
这种方法的优势是能实现完全自定义的退场动画,直接从C过渡到A,没有中间B的显示,动画效果更符合你的需求。
补充说明
为什么之前的代码会出现动画异常?
当你调用
self.presentingViewController?.presentingViewController?.dismiss()时,本质是让A控制器dismiss它的presented控制器(也就是B),此时C作为B的presented控制器会被系统直接移除,不会执行退场动画,所以会出现C突然消失、只有B执行动画的异常效果。
内容的提问来源于stack exchange,提问作者Den Andreychuk
相关产品推荐
相关产品推荐

