iOS转场动画

转场动画,就是Vc切换过程中的过渡动画。

官方支持以下几种方式的自定义转场:

  1. UINavigationController 中 push 和 pop;
  2. Modal 转场:present 和 dismiss;

如果需要更定制化的动画就需要自定义设计转场了

本文主要简单记录分享下自定义转场动画的实现方法, 包含卡片转场动画,开关门动画,手势转场等,代码可以到Github TransitionAnimationTest下载查看

转场动画对象

  1. 创建转场动画控制对象 继承UIViewControllerAnimatedTransitioning对象

    实现以下两个代理方法

    /// 返回动画时长
    func transitionDuration(using transitionContext: UIViewControllerContextTransitioning?) -> TimeInterval {
         return 0.25
    }
    
    /// 所有的过渡动画事务都在这个方法里面完成
    func animateTransition(using transitionContext: UIViewControllerContextTransitioning) {
        switch self.type {
        case .push:
            self.pushAnimation(transitionContext: transitionContext)
        case .pop:
            self.popAnimation(transitionContext: transitionContext)
        case .present:
            self.presentAnimation(transitionContext: transitionContext)
        case .dismiss:
            self.dismissAnimation(transitionContext: transitionContext)
        }
    }
    
  2. 转场环境协议

    转场环境协议 UIViewControllerContextTransitioning 是转场动画的核心,存储转场双方的数据等

    动画协议UIViewControllerAnimatedTransitioning 的两个代理方法都有个遵守UIViewControllerContextTransitioning协议的参数,表示的是当前转场的上下文,可以获取到 fromViewController、或者是toViewController以及fromView、toView、containerView等。

        // 通过viewControllerForKey取出转场前后的两个控制器,这里toVC就是转场后的VC、fromVC就是转场前的VC
    let toVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.to)
    var fromVC = transitionContext.viewController(forKey: UITransitionContextViewControllerKey.from)
    
    // 取出转场前后视图控制器上的视图view
    let toView = transitionContext.view(forKey: UITransitionContextViewKey.to)
    let fromView = transitionContext.view(forKey: UITransitionContextViewKey.from)
    

    经过试验发现不同情况下的fromView、toView有不同的表现

    • push

      截屏2021-09-26 下午2.09.58.png

      可以发现fromView、toView都是有值的,且fromView与fromVC的view一致,toView与toVC的view一致

    • pop

      同上

    • present

      截屏2021-09-26 下午2.24.21.png

      fromView为nil

    • dismiss


      截屏2021-09-26 下午2.26.06.png

      toView为nil

  3. containerView

    如果要对视图做转场动画,视图就必须要加入containerView中才能进行,可以理解containerView管理着所有做转场动画的视图

    let containerView = transitionContext.containerView
    containerView.addSubview(toVC.view)
    // 一般使用snapshotView制作截屏来实现动画效果
    let tempView = animationView.snapshotView(afterScreenUpdates: false)
    containerView.addSubview(tempView)
    

    注意:push和pop以及present的时候,需要添加toVC.view到容器视图上,才能完成正常跳转, dismiss的时候则不需要添加,否则会出现黑屏问题

  4. 动画完成之后需要执行完成方法

    // 当有手势参与时,此处需要进行手势过渡判断,没有则可以直接传true
    transitionContext.completeTransition(true) 
    

导航控制器push和pop 自定义转场

  1. 创建转场动画对象

  2. UINavigationControllerDelegate代理

    pushpop转场需要通过UINavigationControllerDelegate来实现

    需要跳转的控制器需要继承UINavigationControllerDelegate,并实现以下方法

    func navigationController(_ navigationController: UINavigationController, animationControllerFor operation: UINavigationController.Operation, from fromVC: UIViewController, to toVC: UIViewController) -> UIViewControllerAnimatedTransitioning? {
    // 在此处返回转场动画控制对象
        return self.transitionAnimation 
     }
    
  3. delegate生命周期

    • push前

      记录原代理对象

      (vc as? ViewControllerOne)?.navDelegate = self.navigationController?.delegate
      

      为转场到的控制器设置代理,未设置会执行系统转场动画

       self.navigationController?.delegate = vc as? UINavigationControllerDelegate
       self.navigationController?.pushViewController(vc, animated: true)
      
    • pop后

      还原代理,否则会出现内存泄漏导致crash问题

      研究发现过早还原会导致转场动画不生效,太晚还原会导致crash

      网上查询到的资料都建议在viewDidDisappear进行还原,但实际验证发现此时navigationController已经为nil,无法设置代理,无效操作

        // 无效
        override func viewDidDisappear(_ animated: Bool) {
            super.viewDidDisappear(animated)
            self.navigationController?.delegate = self.navDelegate
        }
      

      研究发现可以在UINavigationControllerDelegate的另外一个代理方法中执行还原操作

          /// 发生转场,新的控制器界面即将出现
          func navigationController(_ navigationController: UINavigationController, willShow viewController: UIViewController, animated: Bool) {
              // 返回到上级界面时才进行还原代理操作
              guard viewController != self else {
                    return
              }
              guard let delegate = self.navigationController?.delegate as? UIViewController,
                    delegate == self
              else {
                    return
              }
              // 还原代理
              self.navigationController?.delegate = self.navDelegate
          }
      
    • 当前界面继续进行转场

      当前界面需要继续进行转场时,需要先还原代理,否则会出现转场异常,导致黑屏等问题

        @objc func jumpVC() {
             let vc = ViewController()
             self.navigationController?.delegate = self.navDelegate  // 跳转之前需要先还原代理
             self.navigationController?.pushViewController(vc, animated: true)
        }
      
    • 从别的界面返回到当前界面,需要重设代理为自身,不然返回时会执行系统转场动画

      override func viewWillAppear(_ animated: Bool) {
          super.viewWillAppear(animated)
          // 由其他界面返回到当前界面时 设置代理
          self.navigationController?.delegate = self
      }
    

模态present和dismiss 自定义转场

  1. 创建转场动画对象

  2. UIViewControllerTransitioningDelegate代理

    presentdismiss转场需要通过UIViewControllerTransitioningDelegate来实现

    需要跳转的控制器需要继承UIViewControllerTransitioningDelegate,并实现以下方法

    /// present转场动画
    func animationController(forPresented presented: UIViewController, presenting: UIViewController, source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        self.transitionAnimation.type = .present
        return self.transitionAnimation
    }
    
    /// dismiss转场动画
    func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
        self.transitionAnimation.type = .dismiss
        return self.transitionAnimation
    }
    
  3. delegate生命周期

    界面创建时设置代理

    self.transitioningDelegate = self
    self.modalPresentationStyle = .custom
    

手势转场

  1. 创建转场动画对象

  2. 创建手势转场控制对象

    继承自交互控制器协议 UIViewControllerInteractiveTransitioning, 官方提供了一个已实现UIViewControllerInteractiveTransitioning协议的类UIPercentDrivenInteractiveTransition 为我们预先实现和提供了一系列便利的方法,可以用一个百分比来控制交互式切换的过程,利用手势来完成这个转场

    //暂停交互 
    - (void)pauseInteractiveTransition NS_AVAILABLE_IOS(10_0);
    //更新方法,一般交互时候的进度更新就在这个方法里面
    - (void)updateInteractiveTransition:(CGFloat)percentComplete;
    //第三个是取消交互
    - (void)cancelInteractiveTransition;
    //第四个的话就是设置交互完成
    - (void)finishInteractiveTransition;
    
  3. 为转场控制器添加手势

     /// 添加手势
    func addPanGesture(vc: UIViewController) {
       self.transitionVC = vc
       let pan = UIPanGestureRecognizer(target: self, action: #selector(handleGesture(gesture:)))
       vc.view.addGestureRecognizer(pan)
    }
    

    针对手势操作进行处理,通过UIPercentDrivenInteractiveTransition的方法来控制进度

    fileprivate func interactiveWithPop(gesture: UIPanGestureRecognizer) {
        let translation = gesture.translation(in: gesture.view)
        guard let transitionVC = self.transitionVC else { return }
        // 获取百分比
        var percentComplete: CGFloat = 0
        percentComplete = abs(translation.x / transitionVC.view.frame.size.width)
        // 根据手势状态进行处理
        switch gesture.state {
        case .began: // 开始转场动画
            self.isInteractive = true
            transitionVC.navigationController?.popViewController(animated: true)
        case .changed:
            // 手势过程中,通过updateInteractiveTransition设置转场过程动画进行的百分比,然后系统会根据百分比自动布局动画控件,不用我们控制了
            self.update(percentComplete)
        case .ended: // 根据进度来结束转场或取消转场
            self.isInteractive = false
            if percentComplete > 0.5 {
                self.finish()
            } else {
                self.cancel()
            }
        default:
            break
        }
     }
    
  4. push和pop 手势转场

    实现UINavigationControllerDelegate的代理方法

    func navigationController(_ navigationController: UINavigationController, interactionControllerFor animationController: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        switch self.transitionAnimation.type {
        case .pop: // 如果不是通过手势触发则返回nil
            return self.transitionInteractive.isInteractive ? self.transitionInteractive : nil
        default:
            return nil
        }
    }
    

    同时,要在转场动画对象中添加手势判断

    决定是否完成转场,抑或取消转场

    /// pop动效
    fileprivate func popAnimation(transitionContext: UIViewControllerContextTransitioning) {
        …………
        UIView.animate(withDuration: self.transitionDuration(using: transitionContext)) 
        {…………} completion: { (isFinished) in
            …………
            // 由于加入了手势必须判断
            transitionContext.completeTransition(!transitionContext.transitionWasCancelled)
        }
    }
    
  5. present、dismiss手势转场

    实现UIViewControllerTransitioningDelegate的代理方法

    /// 控制present手势
    func interactionControllerForPresentation(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return nil
    }
    
    /// 控制dismiss手势
    func interactionControllerForDismissal(using animator: UIViewControllerAnimatedTransitioning) -> UIViewControllerInteractiveTransitioning? {
        return self.transitionInteractive
    }
    

    同时,要在转场动画对象中添加手势判断,同上4

仿系统转场动画

另外可以通过自定义动画,实现仿系统转场
本身还是使用正常的导航控制器转场或者模态转场;
将转场时的animated参数置为NO,在新的控制器将要出现或需要返回时在界面内创建动画效果

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

推荐阅读更多精彩内容

  • 转场动画学习中...TransitionDemo代码 实现自定义的转场动画(只涉及自定义动画,不管手势驱动) 涉及...
    YaoYaoX阅读 686评论 0 3
  • 直接看转场动画的协议时有点迷糊,做了一个简单的demo记录下理解过程 一、如何更改动画方式 站在自己的角度,最简单...
    10m每秒滑行阅读 340评论 0 0
  • 这个topic其实起源于看到了我们别的组的一个小效果,就是首页有一个小标签,标签点开会以圆形扩散的效果打开一个付费...
    木小易Ying阅读 3,067评论 1 12
  • 概述 这篇文章,我将讲述几种转场动画的自定义方式,并且每种方式附上一个示例,毕竟代码才是我们的语言,这样比较容易上...
    伯恩的遗产阅读 53,831评论 37 381
  • 首先看一个简单的 效果图模拟器不知道怎么了 变得这么慢,就将就着看吧 关于转场动画我们主要考虑两个,一个是导航条中...
    LeeDev阅读 1,474评论 1 5