Swift下拉菜单动画实现

学iOS开发时间也不短了,但对CoreGraphics自定义动画这块一直都不算太熟练,一是因为默认动画可以在一定程度上满足需求,二也是因为自定义动画这块的第三方库相当多,我也一直没有重复造轮子,但前几天在AppCoda上看到一篇关于自定义动画Demo的文章,觉得还不错,认真研读一下也当弥补自己知识面的漏洞了

先上效果图




文件目录图如下



NewsTableView是效果图中下方的内容展示页面,MenuTableView是上方黑色的Menu列表,MenuTransitionManger就是这篇文章的重点--菜单动画控制类
首先我们来看一下这个类的的接口部分

<pre name="code" class="plain">    override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
        let menuTableViewController = segue.destinationViewController as! MenuTableViewController
        menuTableViewController.currentItem = self.title!
        menuTableViewController.transitioningDelegate = self.menuTransitionManager
        self.menuTransitionManager.delegate = self
    }
 
  
这个prepareForSegue函数位于NewsTableViewController中,menuTransitionManager是控制类在当前类的实例,在该函数中,我们通过segue获取了动画的目的视图控制器,也就是menuTableViewController,然后将他的过渡动画代理设置成了我们实例化的控制类,最后一行代码是为了实现点击空白处dismiss掉弹出菜单,这个最后再讲

class MenuTransitionManager: NSObject, UIViewControllerAnimatedTransitioning, UIViewControllerTransitioningDelegate {
   
    var duration = 0.5
    var isPresenting = false
   
    var snapshot:UIView? {
        didSet {
            if let _delegate = delegate {
                let tapGestureRecognizer = UITapGestureRecognizer(target: _delegate, action: "dismiss")
                snapshot?.addGestureRecognizer(tapGestureRecognizer)
            }
        }
    }
   
    var delegate:MenuTransitionManagerDelegate?
   
    func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
        return duration
    }
   
    func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
       
        //得到源视图和目的视图的引用
        let fromView = transitionContext.viewForKey(UITransitionContextFromViewKey)!
        let toView = transitionContext.viewForKey(UITransitionContextToViewKey)!
       
        //设置变换过程
        let container = transitionContext.containerView()
        let moveDown = CGAffineTransformMakeTranslation(0, container!.frame.height - 150)
        print("container!.frame.height - 150 \(container!.frame.height - 150)")
        let moveUp = CGAffineTransformMakeTranslation(0, -50)
       
        //将两个视图添加到容器视图中
        if isPresenting {
            toView.transform = moveUp
            snapshot = fromView.snapshotViewAfterScreenUpdates(true)
            print("toView.frame: \(toView.frame)")
            print("fromView.frame: \(fromView.frame)")
            print("snapshot.frame: \(snapshot!.frame)")
            container?.addSubview(toView)
            container?.addSubview(snapshot!)
        }
       
        //展示动画
        UIView.animateWithDuration(duration, delay: 0.0, usingSpringWithDamping: 0.9, initialSpringVelocity: 0.3, options: [], animations: {
           
            if self.isPresenting {
                self.snapshot?.transform = moveDown
                toView.transform = CGAffineTransformIdentity
            } else {
                self.snapshot?.transform = CGAffineTransformIdentity
                fromView.transform = moveUp
            }
           
           
            }, completion: { finished in
               
                transitionContext.completeTransition(true)
                if !self.isPresenting {
                    self.snapshot?.removeFromSuperview()
                }
        })
    }
   
    func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        isPresenting = false
        return self
    }
   
    func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
       
        isPresenting = true
        return self
    }
   
}

这是菜单动画控制类的具体代码实现,要做ViewController的过渡动画代理首先必须遵守UIViewControllerTransitioningDelegate,而UIViewControllerAnimatedTransitioning协议用于提供具体的动画动作

最后的两个函数用于在弹出和收回菜单时改变isPresenting标志的真假以展示正确的弹出或收回动画

animateTransition函数是真正的动画执行函数,首先调用当前transitionContext的viewForKey函数来获得源视图和目的视图的引用,在当前的项目里也就分别是NewsTableView和MenuTableView,MoveDown动画用于在弹出菜单时下方的NewsTableView的snapshot,也就是覆盖在原NewsTableView上的截图为弹出的MenuTableView让出150的y轴空间,MoveUp动画用在了两个地方,一个是弹出时MenuView先向上50再向下展示,有种bounce的效果,第二个是NewsTableView在snapshot恢复原位置时向上移动50,说实话我没看懂这里,因为执行到这里的时候在最上层展示的是MenuTableView和snapshot,修改NewsTableView的动画效果大概没什么用,我把这句注释掉和把y轴位移由-50改为-5000都没发现什么变化,如果我理解错了请告诉我..

snapshotViewAfterScreenUpdates是iOS7提供的API,用于快速简单的创建一张某个View的截图,常用于页面转换动画

后面的UIView.animateWithDuration是动画的具体执行过程,CGAffineTransformIdentity是View动画执行前的位置,如果弹出,截图y轴向下移动150,MenuTableView放置到原位置,也就是这个View在自己ViewController的位置,如果收回,截图回到原位置

最后我来说一下这段为实现点击空白处返回的代码

    var snapshot:UIView? {
        didSet {
            if let _delegate = delegate {
                let tapGestureRecognizer = UITapGestureRecognizer(target: _delegate, action: "dismiss")
                snapshot?.addGestureRecognizer(tapGestureRecognizer)
            }
        }
    }

这是Swift语法里的Property observer,当snapshot被赋值时,我们为其添加点击事件,这个点击事件由delegate的dismiss函数执行,这是经典的delegate设计模式了,NewsTableViewController作为代理的执行者,执行dismiss函数

    func dismiss() {
        dismissViewControllerAnimated(true, completion: nil)
    }

然后又触发了那个isPresenting = false的函数,随之引发收回菜单的动画效果

整个工程可在原链接中下载到: http://www.appcoda.com/slide-down-menu-swift/
可能需要科学上网 :) 觉得我讲的清楚的可以点个赞 (ノ ○ Д ○)ノ 


你可能感兴趣的:(ios,swift,graphics,core,menu)