封装自己的自定义转场组件

栏目: IOS · 发布时间: 6年前

内容简介:自定义转场这个技术相信大家都用过或者听说过,不过如果你经常使用自定义转场或者自定义转场动画做得比较复杂或许会发现AnimatedTransitioning与目标控制器的交互并没有那么友好。本文旨在提供一个新思路,减少AnimatedTransitioning与目标控制器的耦合,引入封装好的类文件后,只需少量代码,就可以实现一个基础的转场动画。首先,我们来看一下实现一个普通的转场动画的流程。将转场方式设置为自定义

自定义转场这个技术相信大家都用过或者听说过,不过如果你经常使用自定义转场或者自定义转场动画做得比较复杂或许会发现AnimatedTransitioning与目标控制器的交互并没有那么友好。本文旨在提供一个新思路,减少AnimatedTransitioning与目标控制器的耦合,引入封装好的类文件后,只需少量代码,就可以实现一个基础的转场动画。

首先,我们来看一下实现一个普通的转场动画的流程。

设置转场方式

将转场方式设置为自定义

toVC.modalPresentationStyle = .custom

设置转场代理

这里转场代理可以设置为目标控制器,也可以是我们自定义的管理转场的类,这里设置为目标控制器

toVC.transitioningDelegate = toVC

实现转场代理方法

转场的代理方法我们需要实现以下两个方法,指定我们自定义的类来管理转场动画,关于这个自定义的类,后面会详细说。

public func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
	//这里的animationTransitionContr就是自定义的用来管理转场的类
	animationTransitionContr.isPresenting = true
       return animationTransitionContr
   }
   
   public func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
   	//这里的animationTransitionContr就是自定义的用来管理转场的类
	animationTransitionContr.isPresenting = false
       return animationTransitionContr
   }

自定义转场动画管理类

这个管理类定为NSObject子类就可以,关键是它必须遵从 UIViewControllerAnimatedTransitioning 协议,并实现以下几个代理方法。

func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
        return 0.25
    }
    
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        guard let fromVC = transitionContext.viewController(forKey: .from) else { return }
        guard let toVC = transitionContext.viewController(forKey: .to) else { return }
        if isPresenting {
            // 进入目标控制器的动画
        } else {
            // 退出目标控制器的动画
        }
        
    }

至此,一个常规的自定义转场动画就完成了。到这里就会发现一个问题,在实现具体动画的这一步,只有单个控制器使用这个转场类还好,如果是多个控制器分别需要实现不同的动画效果,这里的逻辑就会很复杂,代码可读性也不好。

要解决这个问题,我们可以把重构一下动画逻辑,将动画代码放在转场类里面,而将动画的具体实现放在目标控制器里。具体实现如下:

为转场控制类定义代理

这里的代理方法是将动画的不同时间点的控制暴露给目标控制器

	protocol ZYAnimationTransitionControllerDelegate: NSObjectProtocol {
    func willPresent(fromView: UIView, toView: UIView)
    func onPresent(fromView: UIView, toView: UIView)
    func didPresent(fromView: UIView, toView: UIView)
    func willDismiss(fromView: UIView, toView: UIView)
    func onDismiss(fromView: UIView, toView: UIView)
    func didDismiss(fromView: UIView, toView: UIView)
}

在适当的时机调用代理方法

fileprivate func present(transitionContext: UIViewControllerContextTransitioning, container: UIView, fromView: UIView, toView: UIView, completion: @escaping () -> Void) {
    container.addSubview(toView)
    guard let delegate = delegate else { return }
    delegate.willPresent(fromView: fromView, toView: toView)
    self.startAnimation(transitionContext: transitionContext, animations: {
        delegate.onPresent(fromView: fromView, toView: toView)
    }) {
        delegate.didPresent(fromView: fromView, toView: toView)
        completion()
    }
}

fileprivate func dismiss(transitionContext: UIViewControllerContextTransitioning, container: UIView, fromView: UIView, toView: UIView, completion: @escaping () -> Void) {
    container.addSubview(fromView)
    guard let delegate = delegate else { return }
    delegate.willDismiss(fromView: fromView, toView: toView)
    self .startAnimation(transitionContext: transitionContext, animations: {
        delegate.onDismiss(fromView: fromView, toView: toView)
    }) {
        delegate.didDismiss(fromView: fromView, toView: toView)
        completion()
    }
}

这里使用了一个辅助方法:

fileprivate func startAnimation(transitionContext: UIViewControllerContextTransitioning, animations: @escaping () -> Void, completion: @escaping () -> Void) {
    UIApplication.shared.beginIgnoringInteractionEvents()
    UIView.animate(withDuration: self.transitionDuration(using: self as? UIViewControllerContextTransitioning), delay: 0, options: UIView.AnimationOptions(rawValue: 7 << 16), animations: animations, completion: { _ in
        UIApplication.shared.endIgnoringInteractionEvents()
        completion()
    })
}

重构转场代理方法

用以下方法替换之前的代理方法实现:

func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        let containerView = transitionContext.containerView
        guard let fromVC = transitionContext.viewController(forKey: .from) else { return }
        guard let toVC = transitionContext.viewController(forKey: .to) else { return }
        if isPresenting {
            present(transitionContext: transitionContext, container: containerView, fromView: fromVC.view, toView: toVC.view) {
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
        } else {
            dismiss(transitionContext: transitionContext, container: containerView, fromView: fromVC.view, toView: toVC.view) {
                transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            }
        }
    }

至此,动画逻辑的重构就完成了。这时,一个完整的自定义转场的实现逻辑就是这样的:

  • 目标控制器设置转场类型为自定义
  • 目标控制器设置转场代理为自己
  • 目标控制器在转场代理方法中返回自定义的转场控制类
  • 在自定义转场控制类中实现UIViewControllerAnimatedTransitioning协议方法
  • 在自定义类中定义代理暴露动画控制权给目标控制器,并在适当时机调用代理方法

相关代码的完整实现可以参考我的图片预览框架 源码


以上所述就是小编给大家介绍的《封装自己的自定义转场组件》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Is Parallel Programming Hard, And, If So, What Can You Do About

Is Parallel Programming Hard, And, If So, What Can You Do About

Paul E. McKenney

The purpose of this book is to help you understand how to program shared-memory parallel machines without risking your sanity.1 By describing the algorithms and designs that have worked well in the pa......一起来看看 《Is Parallel Programming Hard, And, If So, What Can You Do About 》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

UNIX 时间戳转换
UNIX 时间戳转换

UNIX 时间戳转换

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具