如何在视图控制器间过渡时共享UIImage并保持其位置?
你完全找对方向啦!使用UIViewControllerTransitioningDelegate配合自定义转场动画,正是实现这种「跨控制器共享视图+自定义滑入过渡」效果的标准方案,下面我会一步步拆解具体实现思路和关键代码:
核心思路解析
默认的ViewController转场动画会直接切换整个视图层级,无法单独保留某个子视图的状态。而通过自定义转场,我们可以:
- 把需要保留的
UIImageView临时从原VC(A)的视图层级中剥离,放到转场专用的容器视图里,让它在VC B滑入时保持原位 - 执行VC B的上滑入动画
- 动画结束后,将这个
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
相关产品推荐
相关产品推荐

