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)
    
  • 相关阅读:
    使用 Dockerfile 定制镜像
    UVA 10298 Power Strings 字符串的幂(KMP,最小循环节)
    UVA 11090 Going in Cycle!! 环平均权值(bellman-ford,spfa,二分)
    LeetCode Best Time to Buy and Sell Stock 买卖股票的最佳时机 (DP)
    LeetCode Number of Islands 岛的数量(DFS,BFS)
    LeetCode Triangle 三角形(最短路)
    LeetCode Swap Nodes in Pairs 交换结点对(单链表)
    LeetCode Find Minimum in Rotated Sorted Array 旋转序列找最小值(二分查找)
    HDU 5312 Sequence (规律题)
    LeetCode Letter Combinations of a Phone Number 电话号码组合
  • 原文地址:https://www.cnblogs.com/gchlcc/p/5638053.html
Copyright © 2011-2022 走看看