zoukankan      html  css  js  c++  java
  • Swift开发小技巧--自定义转场动画

    自定义转场动画

    • 个人理解为重写了被弹出控制器的modal样式,根据自己的样式来显示modal出来的控制器
    例:presentViewController(aVC, animated: true, completion: nil)
    

    1.为了实现如图,modal出来的aVC控制器有下图这样的效果

    • 首先,需要在modal出来之前,设置aVC的自定义转场动画的样式为自定义(UIModalPresentationStyle.Custom)

    • 其次,设置自定义转场动画的代理,协议 UIViewControllerTransitioningDelegate(代理可以是当前控制器,项目中为了减少当前控制器的代码量,单独抽取了一个类)

    // 2.创建菜单
    let sb = UIStoryboard(name: "PopoverMenuViewController", bundle: nil)
    let menuVC = (sb.instantiateInitialViewController())!
    
    // 自定义转场动画
    // 2.1设置自定义转场动画的样式
    menuVC.modalPresentationStyle = UIModalPresentationStyle.Custom
    // 2.2设置转场动画的代理
    menuVC.transitioningDelegate = presentationMgr
    
    // 3.弹出菜单,modal默认的弹出样式是从上往下,而且还不能控制弹出控制器的大小
    presentViewController(menuVC, animated: true, completion: nil)
    

    2.UIViewControllerTransitioningDelegate协议中的三个方法

    • 方法一(返回一个负责转场动画的对象,转场动画完成后显示的尺寸和位置,可以在这个负责转场动画对象中设置,所以说要自定义这个类,在自定义的负责转场动画的类中设置自己需要的位置和尺寸) :
        func presentationControllerForPresentedViewController(presented: UIViewController, presentingViewController presenting: UIViewController, sourceViewController source: UIViewController) -> UIPresentationController?
        {
            // 自定义负责转场的对象ChaosPresentationController
            let pc = ChaosPresentationController(presentedViewController: presented, presentingViewController: presenting)
            // 位置由外界决定
            pc.presentViewFrame = presentViewFrame
            return pc
        }
    

    2.1自定义自定义负责转场的对象ChaosPresentationController : UIPresentationController,也就是自定义转场.作用:

    1.如果不自定义转场,modal出来的控制器会移除原有的控制器

    2.如果不自定义转场,modal出来的控制器不会移除原有的控制器

    3.如果不自定义转场,modal出来的控制器的尺寸和屏幕一样

    4.如果自定义转场,modal出来的控制器的尺寸可以在containerViewWillLayoutSubviews方法中控制

    5.containerView 非常重要,容器试图,所有modal出来的视图都添加到了containerView上

    6.presentedView() 非常重要,通过该方法能够拿到弹出的视图

    • 决定弹出视图位置的实现思路有两种
        // 第一种 : 重写containerViewWillLayoutSubviews方法,用于布局转场动画弹出的控件
        override func containerViewWillLayoutSubviews() {
            super.containerViewWillLayoutSubviews()
    
    //        containerView 非常重要,容器试图
    //        presentedView() 非常重要,通过该方法能够拿到弹出的视图
            presentedView()?.frame = presentViewFrame // 位置尺寸有外界来决定
    
            // 将HUD添加到containerView上
            containerView?.insertSubview(HUD, atIndex: 0)
        }
    
        // 第二种 : 重写frameOfPresentedViewInContainerView方法,返回容器中view的frame
        override func frameOfPresentedViewInContainerView() -> CGRect {
             return CGRect(x: 85, y: 50,  200, height: 300)
        }
    
    • 方法二: 控制器出现的时候会调用该方法 该方法用于返回一个负责转场动画如何出现的对象,需要遵守UIViewControllerAnimatedTransitioning协议
        func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            // 返回当前控制器,让当前控制器来负责转场怎么出现,并且当前控制器要遵循UIViewControllerAnimatedTransitioning协议
            ChaosLog("出现")
            // 发送通知,告诉外界状态发生改变
            NSNotificationCenter.defaultCenter().postNotificationName(ChaosPresentationManagerDidPresent, object: self)
            isPresent = true // 改变标记的值
            return self
        }
    
    • 方法三: 控制器隐藏的时候会调用该方法 该方法用于返回一个负责转场动画如何消失的对象,需要遵守UIViewControllerAnimatedTransitioning协议
        func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            // 返回当前控制器,让当前控制器来负责转场怎么消失,并且当前控制器要遵循UIViewControllerAnimatedTransitioning协议
            ChaosLog("消失")
            // 发送通知,告诉外界状态发生改变
            NSNotificationCenter.defaultCenter().postNotificationName(ChaosPresentationManagerDidPresent, object: self)
            isPresent = false // 改变标记的值
            return self
        }
    

    3.UIViewControllerAnimatedTransitioning协议中的两个重要方法

    • 方法一 : 用来告诉系统展示动画和消失动画所用的时长,用来统一转场动画显示和消失所用的时间
        func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
    
            return 0.3
        }
    
    • 重点方法二 : 专门用于管理modal以什么样的动画展现和消失的,无论是展现还是消失都会调用该方法

    <1> 专门用于管理modal以什么样的动画显示消失的,无论展示和消失都会调用这个方法
    <2> 只要实现了这个方法,系统就不会默认添加动画了(modal系统默认动画是从上往下钻)
    <3> 所有的动画效果都需要我们自己实现,包括需要展示的视图也需要我们自己添加到容器的视图上(containerView)
    <4> transitionContext : 所有动画需要的属性等都保存在上下文中,换而言之就是可以通过transitionContext获取我们想要的东西

        func animateTransition(transitionContext: UIViewControllerContextTransitioning)
        {
            if isPresent { // 显示
    
                willPresentViewController(transitionContext)
            } else { // 消失
    
                willDismissViewController(transitionContext)
            }
        }
    
        // 显示视图的方法
        private func willPresentViewController(transitionContext: UIViewControllerContextTransitioning) {
    
            //        // 将要modal出来的VC
            //        let toVC = transitionContext.viewControllerForKey(UITransitionContextToViewControllerKey)
            //        // 控制toVC显示出来的VC
            //        let fromVC = transitionContext.viewControllerForKey(UITransitionContextFromViewControllerKey)
            //        ChaosLog(toVC)
            //        ChaosLog(fromVC)
    
            // 对应toVC的View
            guard let toView = transitionContext.viewForKey(UITransitionContextToViewKey) else {
                return
            }
            // 对应fromVC的View
            //        let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)
    
            // 获取containerView
            transitionContext.containerView()?.addSubview(toView)
            // 动画效果
            toView.transform = CGAffineTransformMakeScale(1.0, 0.0)
            // layer的锚点默认在中间,动画也是从中间开始,将锚点设置为顶部
            toView.layer.anchorPoint = CGPoint(x: 0.5, y: 0.0)
            UIView.animateWithDuration(transitionDuration(transitionContext), animations: {
                toView.transform = CGAffineTransformIdentity
                }) { (_) in
                    // 注意: 自定义转场动画,在执行完动画之后一定要告诉系统动画执行完毕了
                    // 刚开始自己忘了,然后PopoverMenu上的TableView不见了
                    transitionContext.completeTransition(true)
            }
        }
    
        // dismiss视图的方法
        private func willDismissViewController(transitionContext: UIViewControllerContextTransitioning) {
    
            guard let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey) else {
                return
            }
            UIView.animateWithDuration(transitionDuration(transitionContext), animations: { () -> Void in
    
                // menu没有动画消失,而是立即消失的原因是CGFloat不准确,导致动画无法执行.
                // 解决方案: 给CGFloat设置一个很小的值即可
                fromView.transform = CGAffineTransformMakeScale(1.0, 0.00001)
                }, completion: { (_) -> Void in
    
                    transitionContext.completeTransition(true)
            })
        }
    

    自定义转场动画细节问题

    1. 导航栏的titlesView中箭头方向改变的问题 : 刚开始的做法是根据选中状态,修改箭头的位置.但是项目中的实际效果为,点击titlesView出现菜单控制器,点击除了titlesView的任何地方菜单控制器消失,如图

    • 解决方案 : 在代理方法中有发出通知,外界通过监听通知来改变titlesView中箭头的方向
      • 通知的字符串常量
      • 分别在菜单控制器显示和消失的方法中发出通知
      • 外界通过监听通知来改变titlesView中箭头方向

    2. 自定义转场动画结束后,一定要告诉系统,动画结束

    // 注意: 自定义转场动画,在执行完动画之后一定要告诉系统动画执行完毕了
    // 刚开始自己忘了,然后PopoverMenu上的TableView不见了
    transitionContext.completeTransition(true)
    
  • 相关阅读:
    设计模式——简单工厂模式
    异常信息ASM ClassReader failed to parse class file的问题解决
    freemarker学习笔记
    java实现邮箱验证的功能
    Quartz学习——Quartz简单入门Demo(二)
    Quartz大致介绍(一)
    深入理解Java线程池:ScheduledThreadPoolExecutor
    抢火车票引发的思考
    CGLib动态代理引起的空指针异常
    从原理上搞定编码(四)-- Base64编码
  • 原文地址:https://www.cnblogs.com/gchlcc/p/5638053.html
Copyright © 2011-2022 走看看