如何为两个嵌套视图控制器实现含双同步动画的自定义转场?
自定义UIViewController转场动画(替换present/dismiss默认逻辑)
完全可以,用UIKit自带的转场代理机制就能实现——不用再写自定义显示/隐藏方法,直接调用原生的present(_:animated:completion:)和dismiss(animated:completion:)就能触发你想要的动画效果。核心是通过UIViewControllerTransitioningDelegate协议提供自定义动画控制器,替换系统默认转场逻辑。
核心实现思路
- 为两种动画分别实现动画控制器:遵循
UIViewControllerAnimatedTransitioning协议,定义动画时长和具体动画逻辑。 - 在发起转场的视图控制器中实现转场代理:遵循
UIViewControllerTransitioningDelegate协议,根据目标控制器类型返回对应的动画控制器。 - 调用原生present/dismiss时,自动触发自定义动画。
步骤1:实现自定义动画控制器
1.1 透明度(Alpha)动画控制器
class FadeTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { private let isPresenting: Bool init(isPresenting: Bool) { self.isPresenting = isPresenting super.init() } // 动画时长 func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.3 } // 具体动画逻辑 func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let containerView = transitionContext.containerView guard let toVC = transitionContext.viewController(forKey: .to), let fromVC = transitionContext.viewController(forKey: .from) else { transitionContext.completeTransition(false) return } if isPresenting { // Present:目标视图从透明到完全显示 toVC.view.alpha = 0 containerView.addSubview(toVC.view) UIView.animate(withDuration: transitionDuration(using: transitionContext)) { toVC.view.alpha = 1 } completion: { finished in transitionContext.completeTransition(finished) } } else { // Dismiss:当前视图从完全显示到透明 UIView.animate(withDuration: transitionDuration(using: transitionContext)) { fromVC.view.alpha = 0 } completion: { finished in fromVC.view.removeFromSuperview() transitionContext.completeTransition(finished) } } } }
1.2 底部上移动画控制器
class SlideUpTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning { private let isPresenting: Bool init(isPresenting: Bool) { self.isPresenting = isPresenting super.init() } func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.35 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { let containerView = transitionContext.containerView guard let toVC = transitionContext.viewController(forKey: .to), let fromVC = transitionContext.viewController(forKey: .from) else { transitionContext.completeTransition(false) return } if isPresenting { // Present:目标视图从屏幕底部滑入 let finalFrame = transitionContext.finalFrame(for: toVC) toVC.view.frame = finalFrame.offsetBy(dx: 0, dy: containerView.bounds.height) containerView.addSubview(toVC.view) // 加入弹簧效果提升自然感 UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5, options: .curveEaseOut) { toVC.view.frame = finalFrame } completion: { finished in transitionContext.completeTransition(finished) } } else { // Dismiss:当前视图滑回屏幕底部 let finalFrame = fromVC.view.frame.offsetBy(dx: 0, dy: containerView.bounds.height) UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .curveEaseIn) { fromVC.view.frame = finalFrame } completion: { finished in fromVC.view.removeFromSuperview() transitionContext.completeTransition(finished) } } } }
步骤2:实现转场代理并使用
在发起转场的父视图控制器中,遵循UIViewControllerTransitioningDelegate协议,根据目标控制器类型返回对应动画控制器:
class ParentViewController: UIViewController, UIViewControllerTransitioningDelegate { // 弹出带透明度动画的VC func showFadeVC() { let fadeVC = FadeViewController() // 你的第一个视图控制器 fadeVC.transitioningDelegate = self fadeVC.modalPresentationStyle = .fullScreen // 按需选择,半透明弹窗可设为.custom present(fadeVC, animated: true) } // 弹出带底部上移动画的VC func showSlideUpVC() { let slideUpVC = SlideUpViewController() // 你的第二个视图控制器 slideUpVC.transitioningDelegate = self slideUpVC.modalPresentationStyle = .fullScreen present(slideUpVC, animated: true) } // 返回Present动画控制器 func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { if presented is FadeViewController { return FadeTransitionAnimator(isPresenting: true) } else if presented is SlideUpViewController { return SlideUpTransitionAnimator(isPresenting: true) } return nil } // 返回Dismiss动画控制器 func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { if dismissed is FadeViewController { return FadeTransitionAnimator(isPresenting: false) } else if dismissed is SlideUpViewController { return SlideUpTransitionAnimator(isPresenting: false) } return nil } }
额外说明
- 如果需要实现半透明弹窗(保留背景视图可见),可将
modalPresentationStyle设为.custom,并实现presentationController(forPresented:presenting:source:)方法,返回自定义UIPresentationController来处理背景模糊或透明度。 - 动画的时长、弹簧参数、曲线等可根据需求自由调整。
- 调用
dismiss(animated:completion:)时,系统会自动触发对应的反向动画,无需额外配置。
内容的提问来源于stack exchange,提问作者Gargo
相关产品推荐
相关产品推荐

