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

如何不使用Storyboard实现ViewController间的转场动画?

完全可以脱离Storyboard实现自定义转场动画!

当然没问题——Storyboard只是iOS开发中可视化构建界面的工具,自定义ViewController转场的核心是依靠UIViewControllerTransitioningDelegate和UIViewControllerAnimatedTransitioning协议,纯代码完全能实现你想要的效果。你已经搭建了正确的代码框架,我来帮你梳理细节和注意事项:

你的现有代码解析

你给出的代码已经走在正确的方向上:

  1. 触发转场的方法:

    @objc func handleAccount() { 
        let navController = UINavigationController(rootViewController: userButtonLauncher()) 
        navController.transitioningDelegate = self 
        navController.modalPresentationStyle = .custom 
        self.present(navController, animated: true, completion: nil) 
    }
    

    这里通过代码实例化UINavigationController作为容器,设置transitioningDelegate为当前控制器,同时将modalPresentationStyle设为.custom——这是启用自定义转场的必要配置,非常正确。

  2. 转场代理方法:

    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
        transition.transitionMode = .present 
        transition.startingPoint = CGPoint(x: 0, y: 0) 
        return transition 
    } 
     
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
        transition.transitionMode = .pop 
        transition.startingPoint = CGPoint(x: 0, y: 0) 
        return transition 
    }
    

    你正确实现了UIViewControllerTransitioningDelegate的两个核心方法,分别为present和dismiss转场返回自定义的动画控制器,并且通过transitionMode区分了两种转场状态,这是自定义转场的关键逻辑。

关键注意事项

要让转场正常工作,还需要确保以下几点:

  • 自定义动画控制器的实现:你代码中的transition对象必须是遵循UIViewControllerAnimatedTransitioning协议的自定义类,必须实现两个强制方法:
    • transitionDuration(using:):返回转场动画的时长
    • animateTransition(using:):编写具体的动画逻辑(比如缩放、平移、淡入淡出等)
  • 状态重置:如果复用同一个transition实例,要确保每次转场前正确设置transitionMode和startingPoint(你已经做到了这一点)
  • 内存管理:如果transition是当前控制器的属性,避免循环引用(比如不需要持有引用的话,可以每次创建新实例,或者用弱引用管理)

补充完整的自定义动画控制器示例

如果还没实现transition对应的类,这里给一个简单的缩放转场示例:

class CustomTransition: NSObject, UIViewControllerAnimatedTransitioning {
    // 定义转场模式
    enum TransitionMode {
        case present
        case dismiss
    }
    
    var transitionMode: TransitionMode = .present
    var startingPoint: CGPoint = .zero
    
    // 动画时长
    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
        }
        
        switch transitionMode {
        case .present:
            // Present动画:从起始点缩放展开
            toVC.view.frame = CGRect(origin: startingPoint, size: CGSize(width: 10, height: 10))
            toVC.view.clipsToBounds = true
            toVC.view.layer.cornerRadius = 5
            containerView.addSubview(toVC.view)
            
            UIView.animate(withDuration: transitionDuration(using: transitionContext),
                           delay: 0,
                           usingSpringWithDamping: 0.8,
                           initialSpringVelocity: 0.5,
                           options: .curveEaseOut) {
                toVC.view.frame = containerView.bounds
                toVC.view.layer.cornerRadius = 0
            } _ in
                transitionContext.completeTransition(true)
            }
            
        case .dismiss:
            // Dismiss动画:缩放到起始点后消失
            UIView.animate(withDuration: transitionDuration(using: transitionContext), animations: {
                fromVC.view.frame = CGRect(origin: self.startingPoint, size: CGSize(width: 10, height: 10))
                fromVC.view.layer.cornerRadius = 5
            }) { _ in
                fromVC.view.removeFromSuperview()
                transitionContext.completeTransition(true)
            }
        }
    }
}

最终整合

在你的主ViewController中,只需声明遵循UIViewControllerTransitioningDelegate协议,并持有CustomTransition实例即可:

class MainViewController: UIViewController, UIViewControllerTransitioningDelegate {
    private let transition = CustomTransition()
    
    // ... 其他视图初始化、布局代码
    
    @objc func handleAccount() { 
        let targetVC = userButtonLauncher() // 你的目标ViewController
        let navController = UINavigationController(rootViewController: targetVC) 
        navController.transitioningDelegate = self 
        navController.modalPresentationStyle = .custom 
        present(navController, animated: true, completion: nil) 
    }
    
    // 实现转场代理方法
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
        transition.transitionMode = .present 
        transition.startingPoint = CGPoint(x: 0, y: 0) // 可以根据需要调整起始点,比如按钮的位置
        return transition 
    } 
    
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { 
        transition.transitionMode = .dismiss 
        transition.startingPoint = CGPoint(x: 0, y: 0) 
        return transition 
    }
}

这样就能完全脱离Storyboard实现自定义转场动画了,整个流程都是纯代码控制,灵活性更高。

内容的提问来源于stack exchange,提问作者eksglox

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.14 07:08:45