我们知道,当一个控制器要弹出另一个控制器时,主要分为两大类,第一种是带导航栏的,push/show等等。这里不作解释,本文章主要针对modal方式自定义转场动画,效果如下:
transitionAnimation.gif
实现这样的效果其实并不复杂,但是有很多细节需要注意。
首先,当我们不自定义转成动画的时候一般都说是像下面这么写就够了。
self.present(crossDissolveSecondVC, animated: true, completion: nil)
从上面的代码中我们似乎找不到自定义动画的接口,那么这里就需要了解一个关键了---转场动画代理 UIViewControllerTransitioningDelegate
如下:
// 设置被modal出来的控制器的样式,此处不写这句代码也可。
crossDissolveSecondVC.modalPresentationStyle = .fullScreen
// crossDissolveSecondVC表示被modal出来的控制器。
crossDissolveSecondVC.transitioningDelegate = self
设置完这句以后,我们遵循协议,进入看一下这个协议中的关键方法:
// present动画
@objc(animationControllerForPresentedController:presentingController:sourceController:) func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
return DHPresentAnimator()
}
// dismiss动画
@objc(animationControllerForDismissedController:) func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
// return DHPresentAnimator()
return DHDismissAnimator()
}
我们可以发现上述两个代理方法,一个是针对present的代理方法,
一个是针对dismiss的时候的代理方法。这两个方法的共同点是都需要返回一个遵守UIViewControllerAnimatedTransitioning协议的对象。
那么下一步我们来创建这样一个遵守协议的类。
如下:
class DHPresentAnimator: NSObject, UIViewControllerAnimatedTransitioning, CAAnimationDelegate {
}
当你写到这里的时候会报错,因为遵守UIViewControllerAnimatedTransitioning协议的类必须实现两个代理方法,这里只需要按照提示实现即可。具体我们看这两个方法各有什么作用。
// MARK: - CAAnimationDelegate的代理方法
extension DHPresentAnimator {
// 这个代理方法设置动画的执行时间
@objc(transitionDuration:) public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return transitionDuration
}
// 这个方法里面主要实现动画细节
@objc(animateTransition:) public func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
// 记录transitionContext
transitionCtx = transitionContext
// 这里指的是firstVC
// let fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
// 这里指的是secondVC
toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)
toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
let containerView = transitionContext.containerView
print(fromView.self)
print(toView.self)
print(containerView.self)
// 这一步是必须的,当prsent动画时需要将modal出来的视图加到containerView中去,
containerView.addSubview(toView!)
// 加入旋转动画。
toView?.layer.add(rotateAnimation, forKey: "rotate")
}
}
以上代码我创建的是一个旋转的动画(CAAnimation),如下:
// 动画的执行时间
let transitionDuration = 2.0
// 旋转动画
lazy var rotateAnimation: CABasicAnimation = {
let animation = CABasicAnimation(keyPath: "transform.rotation")
animation.fromValue = 0.0
animation.toValue = M_PI * 2
animation.duration = self.transitionDuration
animation.delegate = self
return animation
}()
这里最关键的一部就是,当动画执行完毕以后我们需要执行以下代码:
// 这段代码的意思相当于动画的结束标志。
transitionCtx?.completeTransition(!(transitionCtx?.transitionWasCancelled)!)
那么问题来了?我们怎么知道动画什么时候执行完毕,这个时候还是得找代理CAAnimationDelegate,刚刚我们在懒加载中定义动画的一些旋转参数时就已经设置了代理,然后遵循CAAnimationDelegate这个代理协议。我们不难找到:
// MARK: - CAAnimationDelegate 的代理方法
extension DHPresentAnimator {
public func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
print("animationEnd")
// 动画结束以后会调该方法
if flag == true { // 代表正常结束
transitionCtx?.completeTransition(!(transitionCtx?.transitionWasCancelled)!)
print(toView?.frame)
// 移除这个动画
toView?.layer.removeAnimation(forKey: "rotate")
// toView?.alpha = 1.0
}
}
func animationDidStart(_ anim: CAAnimation) {
print("animationBegin")
}
}
说到这里转场动画的基本套路就说完了,其实是怎么一回事呢?要想自定义转场动画,无非就是遵循协议,实现代理方法,在代理方法中拿到即将要出现的视图,拿到这个视图以后我们做相关操作,比如旋转,平移,等等都可以。这就是转场动画的核心部分。
相关代码链接如下:
https://github.com/iLMagic/-.git
网友评论