Swift 4中如何在present UIViewController时实现缩放动画?
实现ViewController的缩放Present动画
我来帮你搞定这个缩放present动画的问题!你之前用CATransition的方式没生效,主要是因为这种方法和系统的ViewController转场流程不太匹配,直接给window的layer加动画没法完全接管VC的转场逻辑。下面给你两种解决方案,优先推荐第一种官方规范的自定义转场方式:
方法一:自定义转场动画(推荐,更灵活可控)
iOS 7之后官方提供了UIViewControllerTransitioningDelegate协议,专门用来实现自定义的VC转场动画,这是最规范的做法。
第一步:写一个缩放动画的实现类
创建一个遵循UIViewControllerAnimatedTransitioning的类,专门处理缩放动画的逻辑:
class ScalePresentAnimator: NSObject, UIViewControllerAnimatedTransitioning { // 动画时长,可根据需求调整 func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval { return 0.5 } func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { // 获取要present的ViewController和它的view guard let targetVC = transitionContext.viewController(forKey: .to), let targetView = transitionContext.view(forKey: .to) else { transitionContext.completeTransition(false) return } // 设置动画初始状态:缩小到30%,完全透明 targetView.transform = CGAffineTransform(scaleX: 0.3, y: 0.3) targetView.alpha = 0.0 // 将目标view添加到转场容器中 let container = transitionContext.containerView container.addSubview(targetView) // 执行缩放+渐显动画,还可以加弹簧效果让动画更自然 UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, usingSpringWithDamping: 0.8, initialSpringVelocity: 0.5, options: .curveEaseInOut, animations: { // 恢复到原始大小和透明度 targetView.transform = .identity targetView.alpha = 1.0 }, completion: { finished in // 通知系统转场完成 transitionContext.completeTransition(finished && !transitionContext.transitionWasCancelled) }) } }
第二步:在源ViewController中设置转场代理
在你要触发present的ViewController里,遵循UIViewControllerTransitioningDelegate,并关联刚才写的动画类:
class YourSourceVC: UIViewController, UIViewControllerTransitioningDelegate { // 初始化缩放动画对象 private let scaleAnimator = ScalePresentAnimator() override func viewDidLoad() { super.viewDidLoad() // 记得给按钮绑定点击事件,比如: // yourButton.addTarget(self, action: #selector(btnClickView(_:)), for: .touchUpInside) } @objc func btnClickView(_ sender: UIButton) { let objImagePreview = ImagePreview(nibName: "ImagePreview", bundle: nil) // 设置转场代理为当前VC objImagePreview.transitioningDelegate = self // 必须设置模态呈现样式为自定义,否则系统会用默认转场 objImagePreview.modalPresentationStyle = .custom // 这里animated要设为true,让系统触发自定义动画 self.present(objImagePreview, animated: true, completion: nil) } // 代理方法:返回present时的动画对象 func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? { return scaleAnimator } // 如果需要dismiss时的反向缩放动画,再加这个方法: // func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { // // 可以写一个反向的动画类,或者修改现有类的逻辑 // return scaleDismissAnimator // } }
方法二:修复你原来的CATransition方式(不推荐,仅作参考)
如果你想继续用CATransition,可以调整动画的添加位置,不过这种方法不是官方推荐的,可能会有兼容性问题:
@objc func btnClickView(_ sender: UIButton) { let objImagePreview = ImagePreview(nibName: "ImagePreview", bundle: nil) let transition = CATransition() transition.duration = 1 // 把类型改成scale,而不是moveIn,这样才是缩放效果 transition.type = .scale transition.subtype = kCATransitionFromTop transition.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) // 把动画添加到当前VC的view.layer上,而不是window self.view.layer.add(transition, forKey: kCATransition) self.present(objImagePreview, animated: false, completion: nil) }
这种方法的问题是:动画是加在当前VC的view上,不是真正的VC转场动画,dismiss的时候不会自动有反向动画,而且在某些iOS版本可能出现异常。
重要提示
- 一定要把目标VC的
modalPresentationStyle设为.custom,否则系统会忽略你的自定义转场代理,用默认的present动画。 - 自定义转场方式可以轻松扩展,比如添加dismiss的反向动画、调整动画参数,适配不同的需求。
内容的提问来源于stack exchange,提问作者jo solution
相关产品推荐
相关产品推荐

