• iOS基础 自定义转场控制器上的动画 UIPresentationController


    概述

    约定:
    底层控制器:在层级较低的控制器,由它创建新控制器
    转场控制器:就是底层控制器创建的新控制器

    转场控制器被创建出来,若要自定义转场的动画,比如从中心逐渐放大知道占据满屏,这样系统没有提供的动画,就需要底层控制器来创建新的转场动画。

    首先需要底层控制器创建新控制器的时候,给新控制器约定管理转场与动画的代理。如:

    accountVC.transitioningDelegate = self
    
    • 1

    当然这里也可以不使用当前控制器作为转场动画的代理,而是重新创建一个对象作为转场动画的代理。这个部分放在文末解释。

    然后底层控制器需要实现相关的协议,即实现控制器转场协议:
    UIViewControllerTransitioningDelegate
    指定管理转场动画的对象,这里制定管理动画的对象为底层控制器。
    如:

    extension BasicController:UIViewControllerTransitioningDelegate{
        //返回控制自定义转场动画 的 控制器
        func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return self
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    然后便是底层控制器实现相关的协议:

    extension BasicController : UIViewControllerAnimatedTransitioning{
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.5
        }
        
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    transitionContext是转场动画的上下文,和绘图上下文一个意思。
    由于自定义动画转场了,所以原本系统会做的动作,现在都需要在方法里面自己实现。

    extension BasicController : UIViewControllerAnimatedTransitioning{
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.5
        }
        
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            //获取弹出的View .from 消失的View .to 弹出的View
            let presentView = (transitionContext.view(forKey: .to))!
            //将弹出的View添加到ContainerView中
            transitionContext.containerView.addSubview(presentView)
            
            presentView.transform = CGAffineTransform.init(translationX: -presentView.frame.width + LeftSlideController.LeftSlideMargin, y: 0)
            
            UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
                //制定自定义的动画
                presentView.transform = CGAffineTransform.identity
            } completion: { isFinish in
                transitionContext.completeTransition(true)
            }
    
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    最后记得在动画完成后,要告诉系统转场动画已经完成。
    transitionContext.completeTransition(true)

    退出的转场动画

    同理:

    func animateTransitionDismiss(transitionContext: UIViewControllerContextTransitioning){
            //获取消失的View .from 消失的View .to 弹出的View
            let dismissView = (transitionContext.view(forKey: .from))!
            UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
                //制定自定义的动画
                dismissView.transform = CGAffineTransform.init(translationX: -500, y: 0)
            } completion: { isFinish in
                dismissView.removeFromSuperview()
                transitionContext.completeTransition(true)
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    对转场动画进行封装

    由于这个是自定义的转场动画,所有的动画细节都需要程序员我们自己去处理,所以才需要构建一个代理。即底层控制器:

    accountVC.transitioningDelegate = ???
    
    • 1

    所以就需要创建一个工具类来专门处理这个转场动画。
    一般情况下构建工具类都是继承自NSObject的,然后再吧先关的代码给移植到工具类中,如下:

    class LeftSlideAnimatior: NSObject {
        var isShowLeftSlideController = false
    }
    
    extension LeftSlideAnimatior:UIViewControllerTransitioningDelegate{
        // 返回控制自定义转场 的 控制器
        func presentationController(forPresented presented: UIViewController, presenting: UIViewController?, source: UIViewController) -> UIPresentationController? {
            return LeftSlideController(presentedViewController: presented, presenting: presenting)
        }
        // 弹出 返回控制自定义转场动画 的 控制器
        func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            isShowLeftSlideController = true
            return self
        }
        // 消失 返回控制自定义转场动画 的 控制器
        func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            isShowLeftSlideController = false
            return self
        }
    }
    
    //
    extension LeftSlideAnimatior : UIViewControllerAnimatedTransitioning{
        func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
            return 0.4
        }
        
        func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
            
            isShowLeftSlideController ? animateTransitionUsing(transitionContext: transitionContext) : animateTransitionDismiss(transitionContext: transitionContext)
            
        }
        
        func animateTransitionUsing(transitionContext: UIViewControllerContextTransitioning){
            //获取弹出的View .from 消失的View .to 弹出的View
            let presentView = (transitionContext.view(forKey: .to))!
            //将弹出的View添加到ContainerView中
            transitionContext.containerView.addSubview(presentView)
            
            presentView.transform = CGAffineTransform.init(translationX: -presentView.frame.width, y: 0)
            
            UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
                //制定自定义的动画
                presentView.transform = CGAffineTransform.identity
            } completion: { isFinish in
                transitionContext.completeTransition(true)
            }
        }
        
        func animateTransitionDismiss(transitionContext: UIViewControllerContextTransitioning){
            //获取消失的View .from 消失的View .to 弹出的View
            let dismissView = (transitionContext.view(forKey: .from))!
            UIView.animate(withDuration: transitionDuration(using: transitionContext)) {
                //制定自定义的动画
                dismissView.transform = CGAffineTransform.init(translationX: -dismissView.frame.width, y: 0)
            } completion: { isFinish in
                dismissView.removeFromSuperview()
                transitionContext.completeTransition(true)
            }
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    而在底层控制器中:

    class BasicController: UIViewController {
        private lazy var leftSildeAnimatior = LeftSlideAnimatior()
    }
    
    • 1
    • 2
    • 3
    let accountVC = AccountController()
    accountVC.modalPresentationStyle = .custom
    accountVC.view.backgroundColor = UIColor.systemOrange
    accountVC.transitioningDelegate = leftSildeAnimatior
    present(accountVC, animated: true) {
        //TODO:
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    转场前后的一些个性化设置

    因为自定义了转场控制器,所以有了更多的个性化创意的想法,又可能想在转场前改变一些控件的设置,或者想要在转场后更新底层控制器的一些UI或者显示的结果。这就需要在控制器改变状态的时候做一些调整。

    这样的解决方案很多,不乏有:

    • 通知
    • 代理
    • 闭包

    其中通知不大合适,因为底层控制器和转场控制器之间的层级不是相差太大,两者之间有一定的联系,所以更加适合的是代理和闭包形式。

    这里介绍如何使用闭包来解决两级控制器之间的消息传递。

    因为我们要在状态改变的时候进行设置,也就是相关的代码应该发生在以下的位置:

    // 弹出 返回控制自定义转场动画 的 控制器
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isShowLeftSlideController = true
        [此处应该有闭包执行]
        return self
    }
    // 消失 返回控制自定义转场动画 的 控制器
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isShowLeftSlideController = false
        [此处应该有闭包执行]
        return self
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    应该在工具类处定义执行闭包:

    class LeftSlideAnimatior: NSObject {
        var isShowLeftSlideController = false
        var transitionSetting:((_ isShowLeftSlideController:Bool)->())?
        
        init(withTransitionSetting:@escaping ((Bool)->())) {
            self.transitionSetting = withTransitionSetting
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在底层控制器创建转场动画代理对象的实例化实现:

    class BasicController: UIViewController {
        
        private lazy var leftSildeAnimatior = LeftSlideAnimatior {[weak self] isShowLeftSlideVC in
            //此处可以做写转场前后的设置【要注意self.的循环引用的问题】
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
  • 相关阅读:
    随处可见的红黑树详解
    React Native(RN)环境搭建
    linux网络、存储性能测试
    pdf如何添加水印?
    Chart FX for WPF 8.1 Crack
    面试阿里P6,过关斩将直通2面,结果3面找了个架构师来吊打我?
    【七夕】是时候展现专属于程序员的“浪漫”了
    1153:绝对素数
    时间复杂度和空间复杂度
    停止员工拖延症!工时管理系统的作用之一
  • 原文地址:https://blog.csdn.net/kkkenty/article/details/124946996