前言
系统自带的push和pop动画已经能满足绝大部份使用场景了。腾讯新闻、汽车之家等App的push和pop动画和系统的不一样,是自定义的,找了点空闲时间,研究了哈自定义控制器转场动画。
1、声明一个枚举,自定义一个指定构造器方法,根据枚举值来确定该使用push或者pop动画
enum TransitionAniamtionsType {
case push,pop
}
var transitionAniamtionsType: TransitionAniamtionsType = .push
public init(type: TransitionAniamtionsType) {
transitionAniamtionsType = type
}
2、新建一个类,遵守<UIViewControllerAnimatedTransitioning>协议,实现两个必需实现的方法
//返回转场动画持续时间
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
return 0.3
}
//转场动画的具体实现
func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
if transitionAniamtionsType == .push {
pushAnimation(using: transitionContext)
}else if transitionAniamtionsType == .pop {
popAnimation(using: transitionContext)
}
}
3、具体动画逻辑,本人实现的动画比较简单,喜欢研究的大神可以根据需要实现更复杂的动画。具体动画实现代码如下
//push
fileprivate func pushAnimation(using transitionContext: UIViewControllerContextTransitioning) {
//获取fromVc和toVc的view
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from),
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { return }
let containerView = transitionContext.containerView
//设置toVc的view的frame,fromVc就是当前界面上的vc就不需要设置和添加了
toView.frame = CGRect(x: screen.width, y: 64, width: screen.width, height: screen.height - 64)
containerView.addSubview(toView)
//执行动画
UIView.animate(withDuration: self.transitionDuration(using: transitionContext), animations: {
fromView.alpha = 0.8
fromView.transform = CGAffineTransform.init(scaleX: 0.9, y: 0.9)
toView.frame = CGRect(x: 0, y: 64, width: self.screen.width, height: self.screen.height - 64)
}) { (finish) in
//!transitionContext.transitionWasCancelled 转场动画是否被取消(手势交互需要这样写,如果不需要手势的话直接写成true就可以了)
//transitionContext.completeTransition这个方法在动画执行完后必需调用
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
//pop
fileprivate func popAnimation(using transitionContext: UIViewControllerContextTransitioning) {
guard let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from),
let toView = transitionContext.view(forKey: UITransitionContextViewKey.to) else { return }
let containerView = transitionContext.containerView
toView.transform = CGAffineTransform.init(scaleX: 0.9, y: 0.9)
toView.alpha = 0.8
containerView.insertSubview(toView, at: 0)
UIView.animate(withDuration: self.transitionDuration(using: transitionContext), animations: {
toView.alpha = 1
toView.transform = CGAffineTransform.identity
fromView.frame = CGRect(x: self.screen.width, y: 64, width: self.screen.width, height: self.screen.height - 64)
}) { (finish) in
transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
}
}
4、在需要动画的控制中设置代理,实现代理方法
\\每次显示这个控制器的view的时候都重新设置代理,不这样的做的话pop回来,下次在push的时候就不会去执行返回动画的代理了
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.navigationController?.delegate = self
}
extension ViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .push {
return TransitionAnimations.init(type: .push)
}else if operation == .pop {
return TransitionAnimations.init(type: .pop)
}else {
return nil
}
}
}
5、手势交互
手势交互实现起来还是比较方便的,苹果已经为我们封装好了相关的方法了,我们只需要添加一个手势来更新相关参数就可以了
//在viewDidLoad中实现
//声明一个交互对象
var interactiveTransition: UIPercentDrivenInteractiveTransition?
func initializeInterface() {
self.navigationController?.delegate = self
view.backgroundColor = UIColor.yellow
let panGesture: UIPanGestureRecognizer = UIPanGestureRecognizer(target: self, action: #selector(respondsToPanGesture))
view.addGestureRecognizer(panGesture)
}
fileprivate func respondsToPanGesture(pan: UIPanGestureRecognizer) {
let moveX = pan.translation(in: pan.view).x
let percent = moveX / screen.width
switch pan.state {
case .began:
interactiveTransition = UIPercentDrivenInteractiveTransition()
navigationController?.popViewController(animated: true)
case .changed:
//根据百分比更新交互
interactiveTransition?.update(percent)
case .ended:
if percent > 0.5 {
interactiveTransition?.finish()
}else {
interactiveTransition?.cancel()
}
interactiveTransition = nil
default:
print("default")
}
}
最后在该控制器中实现navigationdelegate的方法
extension BViewController: UINavigationControllerDelegate {
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if operation == .push {
return TransitionAnimations.init(type: .push)
}else if operation == .pop {
return TransitionAnimations.init(type: .pop)
}else {
return nil
}
}
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
// 返回交互对象
return interactiveTransition
}
}
Demo 地址