You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为两个嵌套视图控制器实现含双同步动画的自定义转场?

自定义UIViewController转场动画(替换present/dismiss默认逻辑)

完全可以,用UIKit自带的转场代理机制就能实现——不用再写自定义显示/隐藏方法,直接调用原生的present(_:animated:completion:)和dismiss(animated:completion:)就能触发你想要的动画效果。核心是通过UIViewControllerTransitioningDelegate协议提供自定义动画控制器,替换系统默认转场逻辑。

核心实现思路

  1. 为两种动画分别实现动画控制器:遵循UIViewControllerAnimatedTransitioning协议,定义动画时长和具体动画逻辑。
  2. 在发起转场的视图控制器中实现转场代理:遵循UIViewControllerTransitioningDelegate协议,根据目标控制器类型返回对应的动画控制器。
  3. 调用原生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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.15 18:15:18