美文网首页SwiftSwift开发swift是未来
swift自定义presentViewController动画和

swift自定义presentViewController动画和

作者: smalldu | 来源:发表于2015-10-06 02:02 被阅读6595次

    我们在左一些app的时候经常会用到详情页,评价页 , 总之就是点一个按钮 就展示一些信息,在做一些简单的展示或者小逻辑。一般都会presentViewController。默认的动画是从下往上,但是我们想要自己主宰它的动画方式怎么弄呢?

    图片来自网络

    本小节将会以一个点击获取英雄详情的demo来介绍一个自定义presentViewController动画(视图控制器切换动画)

    本文源码:https://github.com/smalldu/IOS-Animations
    中的AnimationDemo11    
    

    简单的效果

    简单的效果

    进阶效果

    进阶效果

    我们在左一些app的时候经常会用到详情页,评价页 , 总之就是点一个按钮 就展示一些信息,在做一些简单的展示或者小逻辑。一般都会presentViewController

    大家可以下载我的代码,看看一些跟过渡动画没有关系的设置,比如文字,和半透明背景 ,下面UIScrollView等等 , 因为他们不是本节要介绍的重点,本节要介绍的重点是自定义过渡动画。

    首先,创建一个Single View Application,然后在Main.storyboard中定义好搞两个界面,定义好约束 。 不懂的可以下载我源码。看源码上,也可以不搞这么复杂,随便搞两个页面 练习过渡动画就行。

    我的页面结构

    页面构建

    页面所有元素都是基于AutoLayout约束的

    然后就是创建了一盒Hero.swift 用于存放英雄的基本信息 , 然后在ViewController中将这些英雄的图像加到UIScrollView上,计算好他们的位置。

    每个图像都添加 imageView.userInteractionEnabled = true 属性,可交互,然后添加点击的手势

    imageView.addGestureRecognizer(UITapGestureRecognizer(target: self, action: Selector("didTapImageView:")))
    

    然后就是在点击的时候展示详情页 , 详情页。就会传一个Hero对象的参数。没有什么非常特别的。

     func didTapImageView(tap: UITapGestureRecognizer) {
            selectedImage = tap.view as? UIImageView
            
            let index = tap.view!.tag
            let selectedHerb = he[index-1]
            //present details view controller
            let details = storyboard?.instantiateViewControllerWithIdentifier("detailViewController") as! DetailViewController
            details.he = selectedHerb
            presentViewController(details, animated: true, completion: nil)
        }
    

    这里我把第二个控制器的设置如图

    t4.png

    这时候执行还是默认的效果

    默认效果

    如果你的控制器要设置自己的动画需要实现UIViewControllerTransitioningDelegate协议。每次你present一个新的ViewController的时候,UIKit就会看这个delegate是否使用自定义过渡。

    UIKit通过调用
    animationControllerForPresentedController(:_presentingController:sourceController:);方法,如果这个方法返回nil ,就会调用默认的present 。如果返回的是一个非空对象,就会使用这个对象的控制过渡 ,这个对象必须是实现UIViewControllerAnimatedTransitioning协议的对象。

    UIViewControllerAnimatedTransitioning这个有两个必须的方法

    • transitionDuration 这个方法需要提供返回一个时间,动画持续时间

    • animateTransition 这个是动画的主体方法

    我们新建一个PopAnimation.swift的类让它继承NSObject ,然后实现UIViewControllerAnimatedTransitioning协议 实现了协议就自然要实现那两个方法,第一个方法简单返回一个时间就行了,这里暂且返回1。

    第二个方法有传一个参数 transitionContext: UIViewControllerContextTransitioning

    UIViewControllerContextTransitioning是个什么东西呢 ?
    当两个ViewController之间过渡的时候,刚开始新的控制器已经被创建只是还不可见,因此你的任务就是在animateTransition()方法中把新的控制器添加到transition的容器中,把它以动画的方式添加进来 , 把旧控制器以动画方式移除去

    transitionContext提供两个非常便捷的方法让你获得transition对象:

    • viewForKey() :这个可以通过UITransitionContextFromViewKey 和
      UITransitionContextToViewKey 获得新旧视图]
    • viewControllerForKey(): UITransitionContextFromViewControllerKey 和
      UITransitionContextToViewControllerKey 获得新旧试图控制器

    所以 我们这里先上一个最简单的动画

    import UIKit
    
    class PopAnimator: NSObject,UIViewControllerAnimatedTransitioning {
    
        let duration = 1.0
        //动画持续时间
        func transitionDuration(transitionContext: UIViewControllerContextTransitioning?) -> NSTimeInterval {
            return duration
        }
        
        //动画执行的方法
        func animateTransition(transitionContext: UIViewControllerContextTransitioning) {
            
            let containerView = transitionContext.containerView()
            let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
            containerView!.addSubview(toView!)
            toView!.alpha = 0.0
            UIView.animateWithDuration(duration,
                animations: {
                    toView!.alpha = 1.0
                }, completion: { _ in
                    transitionContext.completeTransition(true)
            })
        }
        
    }
    

    将它的不透明度由0变为1 ,然后在完成的时候调用动画完成方法
    我们首先在ViewController中声明let transition = PopAnimator()

    我们前面说了,我们的ViewController还需要实现UIViewControllerTransitioningDelegate协议

    为了代码整洁,我们在ViewController最下面添加

    extension ViewController:UIViewControllerTransitioningDelegate{
       
        //Present的时候 使用自定义的动画
        func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            
            return transition
        }
        
        //使用默认的动画
        func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            return nil
        }
    }
    

    这两个代理方法,第一个是Presentpresent返回我们自定义的,第二个暂且返回默认的。

    最后别忘了加代理

    
     func didTapImageView(tap: UITapGestureRecognizer) {
            selectedImage = tap.view as? UIImageView
            
            let index = tap.view!.tag
            let selectedHerb = he[index-1]
            //present details view controller
            let details = storyboard?.instantiateViewControllerWithIdentifier("detailViewController") as! DetailViewController
            print(details)
            details.he = selectedHerb
            details.transitioningDelegate = self //设置过渡代理
            presentViewController(details, animated: true, completion: nil)
        }
    

    效果

    t2.gif

    这样一个简单的效果就实现了。

    要实现复杂的效果,我们需要一些计算。首先在PopAnimation中新增两个变量

        var presenting = true  //是否在presenting 
        var originFrame = CGRect.zero
    

    presenting主要用来区分是present还是dismiss

    然后在animateTransition 中

            let containerView = transitionContext.containerView()
            let toView = transitionContext.viewForKey(UITransitionContextToViewKey)
            let detailView = presenting ? toView :
                transitionContext.viewForKey(UITransitionContextFromViewKey)!
    

    前两个没变,后面一个变了,如果当前是present , detail就是toView如果不是detail就是from 。

    然后加上下面三句

     let initialFrame = presenting ? originFrame : detailView!.frame
     let finalFrame = presenting ? detailView!.frame : originFrame
     let xScaleFactor = presenting ?
                initialFrame.width / finalFrame.width :
                finalFrame.width / initialFrame.width
    
    let yScaleFactor = presenting ?
                    initialFrame.height / finalFrame.height :
                    finalFrame.height / initialFrame.height
    

    如果是present初始就是originFrame原如果不是初始就是detail的frame 。final同理

    最后那个算出现在缩放比例

    然后添加代码

     let scaleTransform = CGAffineTransformMakeScale(xScaleFactor,
                yScaleFactor)
      if presenting {
                detailView!.transform = scaleTransform
                detailView!.center = CGPoint(
                x: CGRectGetMidX(initialFrame),
                y: CGRectGetMidY(initialFrame))
                detailView!.clipsToBounds = true
     }
    

    定义一个变换,如果present的话 先把detail先缩放(按照山上面计算的比例缩放),然后设置center。为了定位到当前点击的小图的位置。

    最后一段

    
      containerView!.addSubview(toView!)
           containerView!.bringSubviewToFront(detailView!)
            
            UIView.animateWithDuration(duration, delay: 0, usingSpringWithDamping: 0.5, initialSpringVelocity: 0, options: UIViewAnimationOptions.AllowAnimatedContent, animations: {
                    detailView!.transform = self.presenting ?
                    CGAffineTransformIdentity : scaleTransform
                    detailView!.center = CGPoint(x: CGRectGetMidX(finalFrame),
                    y: CGRectGetMidY(finalFrame))
                    
                }) { (_) -> Void in
                        
                    transitionContext.completeTransition(true)
            }
    

    第一句无可厚非,为啥要加第二句呢?containerView!.bringSubviewToFront(detailView!)

    因为如果是present的话本来就应该放在最前面 ,如果是dismiss的话,不放在最前面开不到变小的效果。

    最后动画如果是present就动画还原detail , 如果是dismiss 就把detail缩放,设置center 。

    这时候你运行代码 , 并没有从图哪里扩大,而使从0,0点 。

    因为这里还有一件事情要做,转换坐标。

    在ViewController中

    //Present的时候 使用自定义的动画
        func animationControllerForPresentedController(presented: UIViewController, presentingController presenting: UIViewController, sourceController source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
           
            transition.originFrame =
                selectedImage!.superview!.convertRect(selectedImage!.frame,
                toView: nil)
            transition.presenting = true
            selectedImage!.hidden = true
    
            return transition
        }
    
      //使用默认的动画
        func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            selectedImage!.hidden = false
            transition.presenting = false
            return transition
        }
    

    第一行是把选择的试图的坐标转换成父试图的坐标,然后transition.presenting 设置了状态,还隐藏了选择的图,这个是为了。dismiss的时候,下面没有图,dismis完成的时候这个图才能显示 。

    这个也很简单,在动画完成的时候判断下,如果是dismiss就执行一段代码就行了,可以用代理我这里直接用了闭包

    声明一个闭包在PopAnimation

    var hideImage:(()->())?
    

    然后动画完成

      if !self.presenting{
                        self.hidIt()
                    }
    
    
       func hidIt(){
            hideImage?()
        }
    

    最后viewc中dsmiss的时候加上那段就行了

     //使用默认的动画
        func animationControllerForDismissedController(dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
            
            transition.hideImage={
                self.selectedImage!.hidden = false
            }
            transition.presenting = false
            return transition
        }
    

    效果

    最终效果

    到这里 基本就实现了,主要是后面的算,其实自定义过渡没啥。就那几步。希望大家从中能学到一些东西,这个动画还能更完善就是dismiss之后的圆角。看官们自己搞搞吧

    相关文章

      网友评论

      • 巴图鲁:膜拜
      • 巴图鲁:不错
      • 4276c32604b1:啊,楼主教的真好!分分钟就看懂了! 顶一个,希望多出一些好的教程,纯小白都能看懂的
      • 执着丶执念:感谢作者的分享,讲解得很详细,我很喜欢,如果再多花点心思整理下文章的排版,可能会好看多了,这是我的意见
      • 8620ab2250cd:怎么找不到源码呀
        8620ab2250cd:@大石头布 下面的图片是怎么添加上去的呀
        smalldu:@一个人醉 https://github.com/smalldu/IOS-Animations
        中的AnimationDemo11
      • x1911:非常喜欢,讲解得很详细,谢谢大大
      • NinthDay:应该是参照raywenderlich中的例子的吧
        Allen_朝辉:@PPPPPPMST https://www.raywenderlich.com/113845/ios-animation-tutorial-custom-view-controller-presentation-transitions
      • restkuan:楼主可以借鉴一下《Swift 定制 View Controller 转场示例》,效果一样的。
        restkuan:@大石头布 http://t.cn/RypRleh
        smalldu:@restkuan 谁写的?有空看看

      本文标题:swift自定义presentViewController动画和

      本文链接:https://www.haomeiwen.com/subject/wangcttx.html