控制器转场动画简易使用

在一次App版本迭代中,🏹🐔🦁提出引导用户以后去点击某个按钮的动画:进入的时候,从A界面点击按钮的位置,B界面从小到大放大最终充满屏幕显示;离开的时候,A界面从大到小缩小,最终消失在A界面按钮的中心。

如果B界面比较简单可以考虑把A界面放在B界面上,使用UIView动画来实现。但是这个B界面是一个控制器,内含各种操作,所以更好的方法是使用控制器转场动画

转场动画协议

转场动画首先需要实现动画协议UIViewControllerAnimatedTransitioning中的两个方法transitionDuration:(动画时长)和animateTransition:(具体动画内容)

为了方便阅读和二次使用,选择新建一个继承NSObject的类,导入UIKit,然后遵守动画协议UIViewControllerAnimatedTransitioning,然后实现两个必须实现的协议方法:动画时长协议方法transitionDuration: 和 具体动画效果协议方法animateTransition:

1
2
3
4
5
6
7
// TransitionAnimation.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

@interface TransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning>

@end
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// TransitionAnimation.m
#import "TransitionAnimation.h"

@implementation TransitionAnimation

#pragma mark - 转场动画协议方法
#pragma mark 动画时长协议方法
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
// 返回需要持续的时间
return 1.f;
}

#pragma mark 具体动画效果协议方法
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
// 在此实现动画具体内容
}

@end

转场动画协议方法的实现

动画具体实现主要在于动画原型是如何变化的,比如在引言中🏹🐔🦁描述的转场动画效果。在iOS中推出控制器是使用NavgationController和Modal(Present)两种方法,但是动画的具体实现是一样的,只是最后调用转场动画的方式不同。为了区分弹出方式,所以随意想了个动画来实现。

完善TransitionAnimation

由于要实现多个转场动画,为了方便后续扩展,那就定了一个枚举来区分,在转场动画协议方法transitionDuration:中根据枚举值跳到不同的转场动画实现中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// TransitionAnimation.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>

typedef NS_ENUM(NSUInteger, AnimationType) {

AnimationTypeModalPresent = 0, //Modal弹出动画
AnimationTypeModalDismiss, //Modal消失动画
AnimationTypeNavPush, //Nav Push动画
AnimationTypeNavPop //Nav Pop动画
};

@interface TransitionAnimation : NSObject<UIViewControllerAnimatedTransitioning>

/**
* 初始化方法过场动画
*/
+ (instancetype)animationWithAnimationType:(AnimationType)animationType;

@end

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
// TransitionAnimation.m
#import "TransitionAnimation.h"

#define ScreenWidth [UIScreen mainScreen].bounds.size.width
#define ScreenHeight [UIScreen mainScreen].bounds.size.height

static const CGFloat kAnimationInterval = 1.f;

@interface TransitionAnimation()

@property (nonatomic, assign) AnimationType animationType;

@end

@implementation TransitionAnimation

#pragma mark - init
+ (instancetype)animationWithAnimationType:(AnimationType)animationType{

return [[self alloc] initWithAnimationType:animationType];
}

- (instancetype)initWithAnimationType:(AnimationType)animationType{

self = [super init];
if (self) {
_animationType = animationType;
}
return self;
}

#pragma mark - 转场动画协议方法
#pragma mark 动画时长协议方法
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{

return kAnimationInterval;
}

#pragma mark 具体动画效果协议方法
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{

switch (_animationType) {
case AnimationTypeModalPresent:
[self presentAnimationTransition:transitionContext];
break;
case AnimationTypeModalDismiss:
[self dismissAnimationTransition:transitionContext];
break;
case AnimationTypeNavPush:
[self pushAnimationTransition:transitionContext];
break;
case AnimationTypeNavPop:
[self popAnimationTransition:transitionContext];
break;
default:
break;
}
}

#pragma mark - 动画实现
- (void)presentAnimationTransition:(id)transitionContext{
// 在此实现动画具体内容
}

- (void)dismissAnimationTransition:(id)transitionContext{
// 在此实现动画具体内容
}

- (void)pushAnimationTransition:(id)transitionContext{
// 在此实现动画具体内容
}

- (void)popAnimationTransition:(id)transitionContext{
// 在此实现动画具体内容
}

具体动画实现

🏹🐔🦁提出的转场动画

🏹🐔🦁提出的转场动画效果:

TransitionAnimationModa

  1. 进入部分动画

    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
    - (void)presentAnimationTransition:(id)transitionContext{
    /**
    * 使用 transitionContext(转场上下文)获取相关界面
    * viewForKey:方法 可以直接获取View但需要iOS8以上
    * UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    * UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
    * 为兼容iOS7,使用viewControllerForKey:方法 先获取控制器再获取对应控制器的view
    */
    UIView *containerView = [transitionContext containerView];
    UIView *fromView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;
    UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;

    // 将新的界面加到容器上
    [containerView addSubview:toView];

    // 设置动画内容
    toView.frame = [UIScreen mainScreen].bounds;
    toView.transform = CGAffineTransformMakeScale(0.025, 0.0125);
    toView.center = (CGPoint){ScreenWidth-30, 45};

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{

    toView.center = (CGPoint){ScreenWidth/2.0f, ScreenHeight/2.0f};
    toView.transform = CGAffineTransformMakeScale(1.0, 1.0);
    } completion:^(BOOL finished) {

    //完成的时候一定要调用让过程动画完成,否则过场动画无法完成(新界面出来了但,用户无法继续操作)
    BOOL success = ![transitionContext transitionWasCancelled];
    [transitionContext completeTransition:success];
    }];
    }
  2. 离开部分动画

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    - (void)dismissAnimationTransition:(id)transitionContext{

    UIView *containerView = [transitionContext containerView];
    UIView *fromView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;
    UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;

    // 先把原来的视图添加回去
    [containerView insertSubview:toView atIndex:0];

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{

    fromView.center = (CGPoint){ScreenWidth-30,45};
    fromView.transform = CGAffineTransformMakeScale(0.025, 0.0125);

    } completion:^(BOOL finished) {

    BOOL success = ![transitionContext transitionWasCancelled];
    // 注意要把视图移除
    [fromView removeFromSuperview];
    [transitionContext completeTransition:success];
    }];
    }

随意想的动画

为了区分跳转方式,随意做了一个不一样的动画用来给NavgationController推出界面转场动画使用,大致效果:

TransitionAnimationNav

  1. 进入部分动画代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    - (void)pushAnimationTransition:(id)transitionContext{

    UIView *containerView = [transitionContext containerView];
    UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;

    [containerView addSubview:toView];

    toView.frame = [UIScreen mainScreen].bounds;
    toView.transform = CGAffineTransformMakeScale(1, CGFLOAT_MIN);
    toView.alpha = 0;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{

    toView.transform = CGAffineTransformMakeScale(1.0, 1.0);
    toView.alpha = 1;

    } completion:^(BOOL finished) {

    BOOL success = ![transitionContext transitionWasCancelled];
    [transitionContext completeTransition:success];
    }];
    }
  2. 离开部分动画代码

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    - (void)popAnimationTransition:(id)transitionContext{

    UIView *containerView = [transitionContext containerView];
    UIView *fromView = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view;
    UIView *toView = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey].view;

    [containerView insertSubview:toView atIndex:0];
    [UIView animateWithDuration:[self transitionDuration:transitionContext]*2/3 animations:^{

    fromView.transform = CGAffineTransformMakeScale(0.09, 0.16);

    } completion:^(BOOL finished) {

    [UIView animateWithDuration:[self transitionDuration:transitionContext]/3 animations:^{
    fromView.center = (CGPoint){fromView.center.x, ScreenHeight+fromView.frame.size.height/2.0f};
    } completion:^(BOOL finished) {

    BOOL success = ![transitionContext transitionWasCancelled];
    [fromView removeFromSuperview];
    [transitionContext completeTransition:success];
    }];
    }];
    }

应用动画效果

Modal方式弹出控制器

  1. 遵守协议
    控制器A需要遵守UIViewControllerTransitioningDelegate协议
  2. 设置代理对象
    设置控制器B对象的transitioningDelegate为控制器A
  3. 实现协议方法
    在控制器A中实现 animationControllerForPresentedController: presentingController:sourceController:(Modal弹出动画协议方法) 和 animationControllerForDismissedController:(Modal消失动画协议方法),在相应的方法中返回遵守并实现UIViewControllerAnimatedTransitioning协议方法的对象
    TransitionAnimation01
  1. 遵守协议
    控制器A需要遵守UINavigationControllerDelegate协议
  2. 设置代理对象
    在控制器A中将navigationController的delegate设定为控制器A
  3. 实现协议方法
    实现协议方法navigationController: animationControllerForOperation: fromViewController:toViewController: ,在该方法中区分用户操作的是Push还是Pop从而返回相应的遵守并实现UIViewControllerAnimatedTransitioning协议方法的对象

TransitionAnimation02

相关Demon

Github - TransitionAnimationSimpleUse