如何在UIStoryboardSegue子类中插入源与目标视图间的中间视图?
解决UIStoryboardSegue中插入额外过渡视图的问题
嘿,这个问题我之前做自定义转场的时候也遇到过!其实你不用发愁找不到容器——苹果早就为这种场景准备了完美的解决方案:转场上下文的containerView。这正是专门用来承载转场过程中所有临时视图、源视图和目标视图的容器,完全能满足你插入额外视图的需求。
核心思路
转场时系统会创建一个UIViewControllerTransitioningContext对象,它的containerView属性就是我们要找的“视图容器”。你只需要把额外视图添加到这个容器里,就能和源视图、目标视图一起编排动画顺序了。
具体实现步骤
我给你写一个完整的示例,分两部分:自定义Segue子类 + 自定义动画控制器(用来精细控制三段动画)。
1. 创建自定义Segue子类
这个类负责触发转场,并关联我们的动画控制器:
import UIKit class CustomThreeSlideSegue: UIStoryboardSegue { override func perform() { guard let sourceVC = source as? UIViewController, let destinationVC = destination as? UIViewController else { super.perform() return } // 设置目标控制器的转场代理为我们的动画控制器 let animator = ThreeSlideAnimator() destinationVC.transitioningDelegate = animator // 如果是push转场,这里改成navigationController?.pushViewController(...) destinationVC.modalPresentationStyle = .fullScreen sourceVC.present(destinationVC, animated: true, completion: nil) } }
2. 创建自定义动画控制器
这个类是核心,负责编排三段动画,并处理额外视图的添加和移除:
import UIKit class ThreeSlideAnimator: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate { // 实现转场代理方法,返回当前动画控制器 func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return self } // 设置总动画时长,你可以根据需求调整 func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 1.8 } // 核心动画逻辑 func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { // 获取转场容器视图,这就是我们的载体! let containerView = transitionContext.containerView guard let sourceView = transitionContext.view(forKey: .from), let destinationView = transitionContext.view(forKey: .to) else { transitionContext.completeTransition(false) return } // 创建你的额外视图(这里用橙色视图做示例,你可以替换成设计师给的自定义视图) let extraView = UIView(frame: containerView.bounds) extraView.backgroundColor = .orange extraView.alpha = 1.0 // 初始化视图位置:目标视图和额外视图先放在屏幕下方 let screenHeight = containerView.bounds.height destinationView.frame = containerView.bounds.offsetBy(dx: 0, dy: screenHeight) extraView.frame = containerView.bounds.offsetBy(dx: 0, dy: screenHeight) // 按层级添加视图:源视图在最底层,然后是额外视图,最后是目标视图 containerView.addSubview(sourceView) containerView.addSubview(extraView) containerView.addSubview(destinationView) // 拆分动画时长,三步各占1/3 let stepDuration = transitionDuration(using: transitionContext) / 3 // 第一步:源视图向上滑出屏幕 UIView.animate(withDuration: stepDuration, animations: { sourceView.frame = containerView.bounds.offsetBy(dx: 0, dy: -screenHeight) }, completion: { _ in // 第二步:额外视图向上滑出屏幕 UIView.animate(withDuration: stepDuration, animations: { extraView.frame = containerView.bounds.offsetBy(dx: 0, dy: -screenHeight) }, completion: { _ in // 第三步:目标视图向上滑入屏幕 UIView.animate(withDuration: stepDuration, animations: { destinationView.frame = containerView.bounds }, completion: { _ in // 动画完成后,通知系统转场结束,并移除额外视图 transitionContext.completeTransition(!transitionContext.transitionWasCancelled) extraView.removeFromSuperview() }) }) }) } }
关键注意点
- 视图层级顺序:一定要按“源视图 → 额外视图 → 目标视图”的顺序添加到
containerView,否则动画过程中会出现视图遮挡问题。 - 转场完成通知:必须调用
transitionContext.completeTransition(),否则系统会认为转场未完成,可能导致控制器状态异常或内存泄漏。 - 额外视图清理:转场完成后记得移除额外视图,避免它留在容器里占用资源。
- 适配push转场:如果是导航栏的push/pop转场,只需要在Segue的
perform()方法里替换成sourceVC.navigationController?.pushViewController(destinationVC, animated: true),动画控制器的逻辑基本不用改。
内容的提问来源于stack exchange,提问作者ilikecode
相关产品推荐
相关产品推荐

