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

如何在视图控制器间过渡时共享UIImage并保持其位置?

你完全找对方向啦!使用UIViewControllerTransitioningDelegate配合自定义转场动画,正是实现这种「跨控制器共享视图+自定义滑入过渡」效果的标准方案,下面我会一步步拆解具体实现思路和关键代码:

核心思路解析

默认的ViewController转场动画会直接切换整个视图层级,无法单独保留某个子视图的状态。而通过自定义转场,我们可以:

  1. 把需要保留的UIImageView临时从原VC(A)的视图层级中剥离,放到转场专用的容器视图里,让它在VC B滑入时保持原位
  2. 执行VC B的上滑入动画
  3. 动画结束后,将这个UIImageView正式归属到VC B的视图层级中
具体实现步骤

1. 准备基础视图与控制器

  • 在ViewControllerA中创建并布局好需要共享的UIImageView(比如命名为sharedImageView)
  • 在ViewControllerB中预留好sharedImageView最终的布局位置(可以提前定义一个属性targetFrameForSharedImage来存储目标位置)

2. 实现自定义转场动画器

创建一个遵循UIViewControllerAnimatedTransitioning的类,负责处理整个转场的动画逻辑:

class SharedImageTransitionAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    let sharedImageView: UIImageView
    let originalScreenFrame: CGRect
    
    // 初始化时传入共享视图和它在屏幕上的原始位置
    init(sharedImageView: UIImageView) {
        self.sharedImageView = sharedImageView
        // 转换为屏幕坐标系下的frame,确保转场时位置准确
        self.originalScreenFrame = sharedImageView.convert(sharedImageView.bounds, to: nil)
        super.init()
    }
    
    // 定义动画时长
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.5
    }
    
    // 核心动画逻辑
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        guard let toVC = transitionContext.viewController(forKey: .to) as? ViewControllerB,
              let toView = toVC.view else {
            transitionContext.completeTransition(false)
            return
        }
        
        // 步骤1:将共享视图移到转场容器,保持原位
        sharedImageView.removeFromSuperview()
        sharedImageView.frame = originalScreenFrame
        containerView.addSubview(sharedImageView)
        
        // 步骤2:设置VC B的初始状态(屏幕下方,准备滑入)
        toView.frame = containerView.bounds.offsetBy(dx: 0, dy: containerView.bounds.height)
        containerView.addSubview(toView)
        
        // 步骤3:执行动画
        UIView.animate(withDuration: transitionDuration(using: transitionContext),
                       delay: 0,
                       usingSpringWithDamping: 0.8,
                       initialSpringVelocity: 0.5,
                       options: .curveEaseInOut) {
            // VC B滑入屏幕
            toView.frame = containerView.bounds
            // 如果需要让图片最终和VC B的布局对齐,这里可以修改sharedImageView的frame
            // self.sharedImageView.frame = toVC.targetFrameForSharedImage
        } completion: { [weak self] finished in
            guard let self = self else { return }
            // 步骤4:动画结束,处理视图归属
            if transitionContext.transitionWasCancelled {
                // 转场取消,把图片放回VC A
                self.sharedImageView.removeFromSuperview()
                if let fromVC = transitionContext.viewController(forKey: .from) as? ViewControllerA {
                    fromVC.view.addSubview(self.sharedImageView)
                    self.sharedImageView.frame = fromVC.sharedImageOriginalFrame // 提前存储VC A中的原始位置
                }
            } else {
                // 转场成功,把图片交给VC B
                self.sharedImageView.removeFromSuperview()
                toVC.view.addSubview(self.sharedImageView)
                self.sharedImageView.frame = toVC.targetFrameForSharedImage
            }
            // 标记转场完成
            transitionContext.completeTransition(finished && !transitionContext.transitionWasCancelled)
        }
    }
}

3. 设置转场代理

让ViewControllerA遵循UIViewControllerTransitioningDelegate,并在present VC B时配置代理:

extension ViewControllerA: UIViewControllerTransitioningDelegate {
    // 提供present时的动画器
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return SharedImageTransitionAnimator(sharedImageView: self.sharedImageView)
    }
    
    // 如果需要dismiss时的反向动画,可以补充这个方法
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        // 类似正向动画,反向执行即可,把图片从VC B移回VC A
        return ReverseSharedImageTransitionAnimator(sharedImageView: self.sharedImageView)
    }
}

// 在ViewControllerA中触发present的方法
func presentVCB() {
    let vcB = ViewControllerB()
    vcB.transitioningDelegate = self
    vcB.modalPresentationStyle = .fullScreen // 或者.custom,根据需求选择
    present(vcB, animated: true)
}
关键注意事项
  • 务必处理转场取消的情况,避免视图丢失
  • 如果需要支持横竖屏,要确保originalScreenFrame和targetFrameForSharedImage能动态适配屏幕尺寸
  • 共享视图的contentMode、clipsToBounds等属性,转场前后要保持一致,避免视觉突兀

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.29 07:15:39