Swift 自定义转场动画

前言

我们经常在没有 UINavigationController的时候,这里我们先说这种情况。使用presentdismisspresent modally弹出模态控制器)的方式切换控制器视图,默认情况下,目标控制器视图从下向上弹出。系统有ViewController自带的modalTransitionStyle属性,可以修改ViewController弹出和消失时的动画。如:

func nextClick() -> Void {
        second.modalTransitionStyle = .partialCurl
        self.present(second, animated: true, completion: nil)
    }

系统自带的有四种动画:

public enum UIModalTransitionStyle : Int {
    
    case coverVertical // 默认 底部滑入

    case flipHorizontal //水平翻转

    case crossDissolve //渐隐

    @available(iOS 3.2, *)
    case partialCurl //翻页
}

但是有时候呢,系统自带的转场动画并不能满足我们的需求,因为我们需要自定义动画。

关于自定义TransitionAnimation

其实呢,在转场的过程中系统会提供一个视图容器用来盛装进行跳转控制器的视图,如下图所示,当前的FirstViewControllerpresent到SecondViewController的时候,此时,FirstViewControllerview变成fromView,这个视图会自动加入到transtition container view中,然后在跳转过程中我们需要做的就是将SecondViewController的视图(此时是toView)加入到这个容器中,然后为这个toView的展现增加动画。

PresentPic.png

TransitionAnimation的实现

1、

首先, 我们先创建一个动画管理类,继承NSObject并遵循UIViewControllerAnimatedTransitioning还有CAAnimationDelegate协议(CAAnimationDelegate这个协议是因为用圆缩放动画时候需要用到),我是将presentdismiss的动画管理类分开写的,就以present动画为例,我创建了CustomPresentAnimation类。
在这个类中,我使用了两种动画,因而我创建了一个枚举用来标示:

enum AnimationType {
    case circle     //圆缩放
    case rectangle  //矩形偏移
}
2、

UIViewControllerAnimatedTransitioning这个协议中有个方法:

public func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval

这个方法是用来控制转场动画执行时间的。

我们的主要操作是在这个方法中进行的:

public func animateTransition(using transitionContext: UIViewControllerContextTransitioning)

在这个方法中,我们开始进行自定义Animation
(1)先说矩形偏移的方法,效果如图:

rectangle.gif

代码里面都有注释,比较容易理解。

// 修改过渡时的背景颜色
            transitionContext.containerView.backgroundColor = UIColor.white
            // 得到toViewController
            let toViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            // 得到fromViewController
            let fromViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)!
            // 将toViewController.view 放到fromViewController.view之上
            transitionContext.containerView.insertSubview(toViewController.view, aboveSubview: fromViewController.view)
            // 对toViewController.view 进行动画操作
            toViewController.view.transform = CGAffineTransform(translationX: LGJWidth, y: LGJHeight)
            UIView.animate(withDuration: self.transitionDuration(using: transitionContext), animations: {
                toViewController.view.transform = CGAffineTransform.identity
                fromViewController.view.transform = CGAffineTransform(translationX: -LGJWidth, y: -LGJHeight)
                }, completion: { (completion) in
                    // 转场完成后 transitionWasCancelled
                    fromViewController.view.transform = CGAffineTransform.identity
                    transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
            })

(2)这个圆缩放的效果就比较常见了,效果如图:


circle.gif

这个效果主要是用在登录界面的转场中,思路如下:


第一步:找到fromViewtoView并且将toView放到containerView中。

let toVC: UIViewController = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)!
            let containerView: UIView = transitionContext.containerView
            containerView.addSubview(toVC.view)

第二步:绘制圆
这里用到贝塞尔曲线绘图,我有写过一篇关于贝塞尔曲线的简单使用方法的介绍,链接:http://www.jianshu.com/p/c883fbf52681。这里分为两步:
首先绘制起点,let startCircle: UIBezierPath = UIBezierPath(ovalIn: CGRect(x: LGJWidth/2, y: LGJHeight/2, width: 50, height: 50)) 这里用的是UIBezierPathpublic convenience init(ovalIn rect: CGRect)这个方法,这个方法是用来在给定的rect中绘制内切圆的,这里给的rect以当前view的中点为圆心,并且width=height,所以可以绘制出圆。
第二个圆就是放大之后的圆,这里使用UIBezierPathinit(arcCenter center: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)这个方法,创建,使用这个方法我们要用到圆的radius半径、startAngle起始角度、endAngle终点角度,clockwise是否是顺时针画圆,这里我们需要的圆的半径通过勾股定理算出。利用CAShapeLayertoView的mask蒙版设为endCircle.cgPath结束时的圆。

//画圆
            let startCircle: UIBezierPath = UIBezierPath(ovalIn: CGRect(x: LGJWidth/2, y: LGJHeight/2, width: 50, height: 50))
            let x: CGFloat = LGJWidth/2
            let y: CGFloat = LGJHeight/2
            //求出半径 pow a的x次方
            let radius: CGFloat = sqrt(pow(x, 2) + pow(y, 2))
            let endCircle: UIBezierPath = UIBezierPath(arcCenter: containerView.center, radius: radius, startAngle: 0, endAngle: CGFloat(2*M_PI), clockwise: true)
            let maskLayer: CAShapeLayer = CAShapeLayer()
            maskLayer.path = endCircle.cgPath
            toVC.view.layer.mask = maskLayer

第三步:添加动画
创建基础动画对象,设置动画的fromValuetoValue这里的这两个值也就是我们上一步用贝塞尔曲线画的两个路径。duration设置动画时间,timingFunction控制动画节奏,setValue添加动画的时候设置key,在代理方法中通过key获取animate(如果动画很多的话),为了观察动画什么时候结束,我们将animation的代理设为self这就是为什么我们将这个类继承CAAnimationDelegate了,最后将动画添加到maskLayer,动画执行过程就是我们看见的从小圆变大的过程了。

let maskLayerAnimation: CABasicAnimation = CABasicAnimation()
            maskLayerAnimation.fromValue = startCircle.cgPath
            maskLayerAnimation.toValue = endCircle.cgPath
            maskLayerAnimation.duration = transitionDuration(using: transitionContext)
            maskLayerAnimation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseOut)
            maskLayerAnimation.setValue(transitionContext, forKey: "transitionContext")
            maskLayerAnimation.delegate = self
            maskLayer.add(maskLayerAnimation, forKey: "path")

第四步:结束跳转
根据animation的代理方法,观察到动画完成后,根据animationkey找到transitionContext,结束动画,将toViewmask设为nil

func animationDidStop(_ anim: CAAnimation, finished flag: Bool) {
        if type == .circle {
            let transitionContext: UIViewControllerContextTransitioning = anim.value(forKey: "transitionContext") as! UIViewControllerContextTransitioning
            transitionContext.completeTransition(true)
            transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)?.view.layer.mask = nil
        }
    }

使用方法

在所谓的fromViewController中,遵循UIViewControllerTransitioningDelegate协议。将toViewControllertransitioningDelegate设为自身。然后执行UIViewControllerTransitioningDelegate方法,presentA.type默认为circle

//MARK: -UIViewControllerTransitioningDelegate
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        let presentA = CustomPresentAnimation()
        presentA.type = .rectangle
        return presentA
    }

关于UINavigationController的转场动画

上面我们介绍了没有 UINavigationController的情况下模态跳转,接下来就简单说一下关于UINavigationController的转场动画的使用方法,其实管理动画的类都是一样的,唯一的不同就是在Controller里面的使用方法的不同,FirstViewController是第一个控制器,SecondViewController是第二个控制器,在第一个控制器中使用时,首先遵循UINavigationControllerDelegate控制器协议,将navigationController?.delegate = self,然后实现ControllerDelegate的代理方法:

func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationControllerOperation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        print("\(navigationController) \(operation.rawValue)")
        
        if operation == .push {
            return CustomPresentAnimation()
        }
        
        if operation == .pop {
            return CustomDismissAnimation()
        }
        
        return nil
    }
小结:

以上就是关于presentdismiss的自定义转场动画比较详细的用法和navigationCotrollerpushpop的自定义转场动画的简单用法介绍。如果有不同见解,欢迎留言交流。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,755评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,369评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,799评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,910评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,096评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,159评论 3 411
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,917评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,360评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,673评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,814评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,509评论 4 334
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,156评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,882评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,123评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,641评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,728评论 2 351

推荐阅读更多精彩内容

  • 前言的前言 唐巧前辈在微信公众号「iOSDevTips」以及其博客上推送了我的文章后,我的 Github 各项指标...
    VincentHK阅读 5,350评论 3 44
  • 在新浪微博的首页部分,点击用户名会向下弹出一个表格,如下图: 3⃣️.我们自定义了转场动画,还需要一个负责转场动画...
    U小姐咯阅读 832评论 5 2
  • 自定义转场动画 这张图是自己在翻译官方文档Customizing the Transition Animation...
    丨n水瓶座菜虫灬阅读 1,146评论 0 3
  • 我想每个人都会经历分手和在一起,我们聊着别人的八卦,内心编排着自己的剧本,好像自己的故事会有什么不同。 感谢今天一...
    Rainyue玥阅读 240评论 0 1
  • 如果你不害怕自己内在的声音,也就不会畏惧别人对你的评价了。我们都是生活在现实环境中,周围的一切都有着现实的固有评判...
    快乐飞翔2016阅读 242评论 0 0