IOS自定义转场动画-实现与管理上

语言:Swift

ios控制转场动画的两块大砖:
animated动画:UIViewControllerAnimatedTransitioning
interactive交互:UIViewControllerInteractiveTransitioning

animated是在自定义转场动画时必须实现的一个代理,用以告诉系统具体动画细节,而interactive是依附animated存在的交互控制代理。意思是当你自定义了一个animated动画后,通过实现interactive代理来告诉系统怎么用交互控制这个动画。

但实际项目中控制交互一般用的是UIPercentDrivenInteractiveTransition,它是系统提供的interactive的一个实现类,并提供了百分比控制的方式来很方便地控制动画交互。

简单点说,自定义动画器Animator实现UIViewControllerAnimatedTransitioning协议,用交互器UIPercentDrivenInteractiveTransition或其子类来控制动画交互

一、先说说在哪里实现:

  1. 如果是present模态控制器时,需要实现UIViewControllerTransitioningDelegate协议,并赋值给controller的transitioningDelegate属性。这个协议一般用的就四个方法:
// 动画器
func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning?
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning?
// 交互器
func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?
  1. 如果是自定义导航器内push或pop动画时,需要实现的是UINavigationControllerDelegate协议里面的两个方法,并赋值给navigationController的delegate:
// 动画器
func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning?
// 交互器
func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning?

一般系统会先通过动画器的代理询问有无自定义的动画,无就使用系统自带的转场方式,有就再通过交互器的代理询问是否需要交互性

二、再看看简单实现方式
动画器:遵循animated协议必须实现的两个方法

// 转场时间
func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval
// 动画细节
func animateTransition(using transitionContext: UIViewControllerContextTransitioning)

一个简单的present动画器就可以写成

class PresentAnimator: NSObject, UIViewControllerAnimatedTransitioning {
    private var duration: TimeInterval = 0.6
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return duration
    }
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        // 可以使用as?获取具体控制器的引用,针对特定的视图做效果
        guard let to = transitionContext.viewController(forKey: .to) else { return }
        guard let from = transitionContext.viewController(forKey: .from) else { return }
        // 视图容器,动画效果需要在里面完成
        let container = transitionContext.containerView
        container.addSubview(from.view)
        to.view.alpha = 0
        container.addSubview(to.view)
        // 简单效果
        UIView.animate(withDuration: self.duration, animations: {
            to.view.alpha = 1
        }) { (finished) in
            if finished {
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
        }
    }
}

交互器:拿UIPercentDrivenInteractiveTransition的对象直接使用,使用时候一般随着手势

// 当前动画执行的百分比
open var percentComplete: CGFloat
// 控制动画执行到哪里,值0到1
open func update(_ percentComplete: CGFloat)
// 取消动画,恢复原状
open func cancel()
// 完成剩余的动画
open func finish()

比如定义两个控制器FirstViewController和SecondViewController,在FirstViewController的view加一个拖拽手势,通过向下拖拽来present SecondViewController(我们使用上面写的一个简单动画器):

class FirstViewController: UIViewController {
    private var interactive: UIPercentDrivenInteractiveTransition?
    override func viewDidLoad() {
        super.viewDidLoad()
        interactive = UIPercentDrivenInteractiveTransition.init()
        let panGesture = UIPanGestureRecognizer.init(target: self, action: #selector(self.pan(_:)))
        self.view.addGestureRecognizer(panGesture)
    }
    @objc private func pan(_ gesture: UIPanGestureRecognizer) {
        if gesture.state == .began {
            self.presentItem()  // 在手势开始时present控制器
        } else if gesture.state == .changed {
            let offset = gesture.translation(in: self.view)
            if offset.y > 0 && offset.y < 400 {
                interactive?.update(offset.y/400)  // 用向下偏移量来控制动画执行百分比
            }
        } else if gesture.state == .cancelled {
            interactive?.cancel()  // 取消动画
        } else if gesture.state == .ended {
            // 手势结束时判断结束或取消动画
            if interactive!.percentComplete > 0.4 {
                interactive?.finish()
            } else {
                interactive?.cancel()
            }
        }
    }
    private func presentItem() {
        let second = SecondViewController.init()
        second.modalPresentationStyle = .fullScreen
        second.transitioningDelegate = self  // 自定义动画的代理
        self.present(second, animated: true, completion: nil)
    }
}

extension FirstViewController: UIViewControllerTransitioningDelegate {
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return PresentAnimator.init()  // 动画器
    }
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        return nil
    }
    func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return interactive  // 交互器
    }
}

当需要自定义导航器内的push或pop动画时,可以把动画器跟交互器放到自定义的导航器下:

class MyNavigationController: UINavigationController {
    private var interactive: UIPercentDrivenInteractiveTransition?
    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.
        self.delegate = self
        interactive = UIPercentDrivenInteractiveTransition.init()
    }
}
extension MyNavigationController: UINavigationControllerDelegate {
    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        if fromVC.isKind(of: FirstViewController.self), toVC.isKind(of: SecondViewController.self), operation == .push {
            return PushAnimator.init()  // 给一个push动画器
        } else if fromVC.isKind(of: SecondViewController.self), toVC.isKind(of: FirstViewController.self), operation == .pop {
            // 特别的,当执行pop动画时,second控制器是fromVC,first控制器是toVC
            return nil
        }
        return nil
    }
    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        if animationController.isKind(of: PushAnimator.self) {
            return interactive
        }
        return nil
    }
}

这么看起来还是挺简单的,其实不然。当一个导航器下众多不同的动画与交互器堆到了一起,一些动画还需要特定视图的引用,并且还需要动态选择某个动画器的时候,管理起来是个不小的灾难。下节看看实际项目中该怎么进行管理

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容