前言

iOS 7之后,苹果提供了自定义转场动画的API,我们可以自己去定义任意动画效果。本篇为笔者学习pushpop自定义转场效果的笔记,如何有任何不正确或者有指导意见的,请在评论中留下您的宝贵意见!!!

请注意:如果要求支持iOS 7以下版本,则不可使用此效果。

实现目标效果

我们本篇文章目标效果:

视图切换种类

如下效果图,这是有两大类视图切换动画的,一种是交互式的,另一种就是自定义的。

本篇只讲其中的UIViewControllerAnimatedTransitioning协议,来实现pushpop动画效果。另外的几个,后面会继续学习总结!!!

协议

我们要实现pushpop自定义转场效果,我们必须要有一个遵守了UIViewControllerAnimatedTransitioning协议且实现其必须实现的代理方法的类。

我们先来学习UIViewControllerAnimatedTransitioning协议:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
 
@protocol UIViewControllerAnimatedTransitioning <NSObject>
 
// This is used for percent driven interactive transitions, as well as for container controllers that have companion animations that might need to
// synchronize with the main animation.
//
// 指定转场动画时长,必须实现,否则会Crash。
// 这个方法是为百分比驱动的交互转场和有对比动画效果的容器类控制器而定制的。
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
 
// This method can only  be a nop if the transition is interactive and not a percentDriven interactive transition.
// 若非百分比驱动的交互过渡效果,这个方法只能为空
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
 
 
@optional
 
// This is a convenience and if implemented will be invoked by the system when the transition context's completeTransition: method is invoked.
- (void)animationEnded:(BOOL) transitionCompleted;
 
@end
 

我们要实现目标效果,就需要一个定义一个类遵守UIViewControllerAnimatedTransitioning协议并实现相应的代理方法。

遵守UIViewControllerAnimatedTransitioning协议

下面,我们来定义一个转场类,这个类必须要遵守UIViewControllerAnimatedTransitioning协议,如下:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
 
//
//  HYBControllerTransition.h
//  PushPopMoveTransitionDemo
//
//  Created by huangyibiao on 15/12/18.
//  Copyright © 2015年 huangyibiao. All rights reserved.
//
 
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
 
typedef NS_ENUM(NSUInteger, HYBControllerTransitionType) {
  kControllerTransitionPush = 1 << 1,
  kControllerTransitionPop = 1 << 2
};
 
@interface HYBControllerTransition : NSObject <UIViewControllerAnimatedTransitioning>
 
+ (instancetype)transitionWithType:(HYBControllerTransitionType)transitionType
                          duration:(NSTimeInterval)duration;
 
@end
 

我们只需要公开一个工厂方法来生成对象即可,调用更简单些。第个参数用于指定是哪种类型,是push还是pop,第二个参数是用于指定动画时长。

实现文件

我们一步步分析下面的关键代码:

 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
 
//
//  HYBControllerTransition.m
//  PushPopMoveTransitionDemo
//
//  Created by huangyibiao on 15/12/18.
//  Copyright © 2015年 huangyibiao. All rights reserved.
//
 
#import "HYBControllerTransition.h"
#import "ViewController.h"
#import "DetailController.h"
 
@interface HYBControllerTransition ()
 
@property (nonatomic, assign) HYBControllerTransitionType transitionType;
@property (nonatomic, assign) NSTimeInterval duration;
 
@end
 
@implementation HYBControllerTransition
 
- (instancetype)init {
  if (self = [super init]) {
    self.transitionType = kControllerTransitionPush;
  }
  
  return self;
}
 
+ (instancetype)transitionWithType:(HYBControllerTransitionType)transitionType
                          duration:(NSTimeInterval)duration {
  HYBControllerTransition *transition = [[HYBControllerTransition alloc] init];
  transition.transitionType = transitionType;
  transition.duration = duration;
  
  return transition;
}
 
#pragma mark - UIViewControllerAnimatedTransitioning
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
  switch (self.transitionType) {
    case kControllerTransitionPush: {
      [self push:transitionContext];
      break;
    }
    case kControllerTransitionPop: {
      [self pop:transitionContext];
      break;
    }
    default: {
      break;
    }
  }
}
 
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
  return self.duration;
}
 
- (void)animationEnded:(BOOL)transitionCompleted {
  NSLog(@"%s", __FUNCTION__);
}
 
#pragma mark - Private
- (void)pop:(id<UIViewControllerContextTransitioning>)transitionContext {
  DetailController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  ViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  UIView *containerView = [transitionContext containerView];
 
  UIView *toImageView = toVC.isImg1 ? toVC.img1 : toVC.img2;
  
  UIView *tempView = containerView.subviews.lastObject;
  
  // 第一个view是fromVC.view
  // 第二个view是push进来时所生成的toImageView截图
  for (UIView *view in containerView.subviews) {
    NSLog(@"%@", view);
    if (fromVC.view == view) {
      NSLog(@"YES");
    }
  }
  
  toImageView.hidden = YES;
  tempView.hidden = NO;
  // 必须保证将toVC.view放在最上面,也就是第一个位置
  [containerView insertSubview:toVC.view atIndex:0];
 
  [UIView animateWithDuration:self.duration
                        delay:0.0
       usingSpringWithDamping:0.55
        initialSpringVelocity:1/ 0.55
                      options:0
                   animations:^{
                     fromVC.view.alpha = 0.0;
                     tempView.frame = [toImageView convertRect:toImageView.bounds toView:containerView];
  } completion:^(BOOL finished) {
    tempView.hidden = NO;
    toImageView.hidden = NO;
    [tempView removeFromSuperview];
    
    [transitionContext completeTransition:YES];
    
    for (UIView *view in containerView.subviews) {
      NSLog(@"%@", view);
      if (toVC.view == view) {
        NSLog(@"YES");
      }
    }
  }];
}
 
- (void)push:(id<UIViewControllerContextTransitioning>)transitionContext {
  ViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
  DetailController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
  UIView *containerView = [transitionContext containerView];
  
  UIView *fromImageView = fromVC.isImg1 ? fromVC.img1 : fromVC.img2;
  UIView *tempView = [fromImageView snapshotViewAfterScreenUpdates:NO];
  tempView.frame = [fromImageView convertRect:fromImageView.bounds toView:containerView];
 
  UIView *toImageView = toVC.imgView;
  
  fromImageView.hidden = YES;
  toVC.view.alpha = 0.0;
  toImageView.hidden = YES;
  
  [containerView addSubview:toVC.view];
  [containerView addSubview:tempView];
  
  [UIView animateWithDuration:self.duration
                        delay:0.0
       usingSpringWithDamping:0.55
        initialSpringVelocity:1/ 0.55
                      options:0
                   animations:^{
                     toVC.view.alpha = 1.0;
                     tempView.frame = [toImageView convertRect:toImageView.bounds toView:containerView];
                   } completion:^(BOOL finished) {
                     tempView.hidden = YES;
                     toImageView.hidden = NO;
 
                     [transitionContext completeTransition:YES];
                   }];
}
@end
 

分析Push动画

我们暂不细说UIViewControllerContextTransitioning协议,我们这里只使用到了-containerView这个代理方法,我们可以通过苹果提供的键来获取对应的控制器:

 
1
2
3
4
 
UIKIT_EXTERN NSString *const UITransitionContextFromViewControllerKey NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UITransitionContextToViewControllerKey NS_AVAILABLE_IOS(7_0);
 

我们可以看到这是在iOS 7.0以后才有的,因此系统版本要求是在iOS 7才能使用。

我们这里通过:

 
1
2
3
 
ViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
 

获取到了fromVC,也就是当前要从哪个控制器切换。

然后通过:

 
1
2
3
 
DetailController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
 

获取到了toVC,也就是切换到哪一个控制器。

然后再通过

 
1
2
3
 
UIView *containerView = [transitionContext containerView];
 

获取containerView视图。这个是一个代理方法,可以获取到视图容器。

下面我们获取fromVC所点击的图片控件,然后通过-snapshotViewAfterScreenUpdates:将所点击的图片控件截图并用于切换使用,参数设置为NO,否则动画会很生硬。最后,我们还要将这个所点击的图片控件的坐标转换成容器视图的坐标:

 
1
2
3
4
5
 
UIView *fromImageView = fromVC.isImg1 ? fromVC.img1 : fromVC.img2;
UIView *tempView = [fromImageView snapshotViewAfterScreenUpdates:NO];
tempView.frame = [fromImageView convertRect:fromImageView.bounds toView:containerView];
 

将下来就是设置切换动画之前的状态:

 
1
2
3
4
5
6
7
 
UIView *toImageView = toVC.imgView;
  
fromImageView.hidden = YES;
toVC.view.alpha = 0.0;
toImageView.hidden = YES;
 

下面这两行是非常关键的,并且必须保证tempView在最上层,否则动画效果就没有了。先将目标控制器的视图添加到容器中,再添加源图片的截图到容器中,用于显示切换效果。

 
1
2
3
4
 
[containerView addSubview:toVC.view];
[containerView addSubview:tempView];
 

我们在动画中,将初始的截图的frame改变成最终的效果的frame即可达到我们的目标效果。另外要注意还需要将坐标转换成容器的坐标:

 
1
2
3
 
tempView.frame = [toImageView convertRect:toImageView.bounds toView:containerView];
 

当动画完成以后,一定要调用:[transitionContext completeTransition:YES],设置切换动画已经完成,否则想要pop回去就不能了。

分析pop动画

我们只讲不同于push部分的代码,我们添加了打印容器中的视图的代码:

 
1
2
3
4
5
6
7
8
9
10
 
// 第一个view是fromVC.view
// 第二个view是push进来时所生成的toImageView截图
for (UIView *view in containerView.subviews) {
    NSLog(@"%@", view);
    if (fromVC.view == view) {
      NSLog(@"YES");
    }
}
 

打印结果:

 
1
2
3
4
5
6
7
8
 
<UIView: 0x7fae00514ef0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fae00513320>>
YES
<_UIReplicantView: 0x7fae00566bd0; frame = (20 20; 335 627); hidden = YES; layer = <_UIReplicantLayer: 0x7fae00510520>>
 
<UIView: 0x7fae004563a0; frame = (0 0; 375 667); autoresize = W+H; layer = <CALayer: 0x7fae00422b20>>
YES
 

从打印结果分析出来,在pop之前,第一个就是fromVC.view,第二个就是上次push时的截图。在动画完成之后,只剩下toVC.view了。

我们还需要将toVC.view放在容器最上层:

 
1
2
3
4
 
// 必须保证将toVC.view放在最上面,也就是第一个位置
[containerView insertSubview:toVC.view atIndex:0];
 

动画完成后,一定要将tempView移除:

 
1
2
3
 
[tempView removeFromSuperview];
 

如果不移除,这个tempView会与所点击的图片控件等大小,位置一样,会挡住原图片控件,手势也就无法响应。我们一定要注意移除。动画完成后,也一定要设置[transitionContext completeTransition:YES]

iOS7 push/pop转场动画的更多相关文章

  1. iOS_SN_push/pop转场动画封装和一般动画封装

    封装类中的方法: #import <Foundation/Foundation.h> #import <UIKit/UIKit.h> @interface AnimationE ...

  2. iOS 7 present/dismiss转场动画

    前言 iOS 7以后提供了自定义转场动画的功能,我们可以通过遵守协议完成自定义转场动画.本篇文章讲解如何实现自定义present.dismiss自定义动画. 效果图 本篇文章实现的动画切换效果图如下: ...

  3. 自定义Push/Pop和Present/Dismiss转场

    项目概述 iOS中最常见的动画无疑是Push和Pop的转场动画了,其次是Present和Dismiss的转场动画. 如果我们想自定义这些转场动画,苹果其实提供了相关的API,在自定义转场之前,我们需要 ...

  4. 类似nike+、香蕉打卡的转场动画效果-b

    首先,支持并感谢@wazrx 的 http://www.jianshu.com/p/45434f73019e和@onevcat 的https://onevcat.com/2013/10/vc-tran ...

  5. 第六十五篇、OC_iOS7 自定义转场动画push pop

    自定义转场动画,在iOS7及以上的版本才开始出现的,在一些应用中,我们常常需要定制自定义的的跳转动画 1.遵守协议:<UIViewControllerAnimatedTransitioning& ...

  6. Swift基础之自定义PUSH和POP跳转动画

    之前用OC代码写过PUSH和POP的转场动画,闲来无事,将其转换成Swift语言,希望对大家有帮助,转载请注明.... 如何实现PUSH和POP的转场动画? 首先,创建一个NSObject的类,分别用 ...

  7. iOS 开发--转场动画

    "用过格瓦拉电影,或者其他app可能都知道,一种点击按钮用放大效果实现转场的动画现在很流行,效果大致如下:" 本文主讲SWIFT版,OC版在后面会留下Demo下载 在iOS中,在同 ...

  8. iOS自定义转场动画的实现

    iOS中熟悉的是导航栏中的push和pop这两种动画效果,在这里我们可以自己实现自己想要的一些转场动画 下面是我自己创建转场动画的过程 1.新建一个文件继承自NSObject ,遵循协议UIViewC ...

  9. iOS 转场动画探究(二)

    这篇文章是接着第一篇写的,要是有同行刚看到的话建议从前面第一篇看,这是第一篇的地址:iOS 转场动画探究(一) 接着上一篇写的内容: 上一篇iOS 转场动画探究(一)我们说到了转场要素的第四点,把那个 ...

随机推荐

  1. Leetcode 221.最大的正方形

    最大的正方形 在一个由 0 和 1 组成的二维矩阵内,找到只包含 1 的最大正方形,并返回其面积. 示例: 输入: 1 0 1 0 0 1 0 1 1 1 1 1 1 1 1 1 0 0 1 0 输出 ...

  2. 按Esc按钮关闭layer弹窗

    //按Esc关闭弹出框 $(document).ready(function () { }).keydown( function (e) { if (e.which === 27) {  layer. ...

  3. Poj1704:staircase nim【博弈】

    题目大意:有一个无限长的一维的棋盘,棋盘上N个格子放置着棋子.两个人轮流操作,每次操作能选择其中一个棋子向左移动,但不能越过其它棋子或者两枚棋子放在同一格中,最后不能操作的人算输,问先手是否必胜? 思 ...

  4. POJ1159:Palindrome【dp】

    题目大意:给出一个字符串,问至少添加多少个字符才能使它成为回文串? 思路:很明显的方程是:dp[i][j]=min{dp[i+1][j],dp[i][j-1],dp[i+1][j-1](str[i]= ...

  5. 【BZOJ1717&POJ3261】Milk Patterns(后缀数组,二分)

    题意:求字符串的可重叠的k次最长重复子串 n<=20000 a[i]<=1000000 思路:后缀数组+二分答案x,根据height分组,每组之间的height>=x 因为可以重叠, ...

  6. Arduino学习笔记0---开发板的了解

    Arduino的入门文档https://wenku.baidu.com/view/4040861d58fafab069dc02d4.html?from=search,共61页的文档,看完就差不多可以入 ...

  7. Python()- 面向对象三大特性----继承

    继承: 继承是一种创建新类的方式,在python中,新建的类可以继承一个或多个父类(基类或超类),新建的类是所继承的类的(派生类或子类) 人类和狗 有相同的属性, 提取了一个__init__方法,在这 ...

  8. T1365 浴火银河星际跳跃 codevs

    http://codevs.cn/problem/1365/  时间限制: 1 s  空间限制: 128000 KB  题目等级 : 黄金 Gold 题目描述 Description 小 K 又在玩浴 ...

  9. [Bzoj4182]Shopping(点分治)(树上背包)(单调队列优化多重背包)

    4182: Shopping Time Limit: 30 Sec  Memory Limit: 128 MBSubmit: 374  Solved: 130[Submit][Status][Disc ...

  10. how to read openstack code: service plugin

    We have learned core plugin, service plugin and extension in last post. Now let`s review: Core Plugi ...