从iOS7开始,苹果更新了自定义ViewController转场的API,这些新增的类和接口让很多人困惑,望而却步。本文就从这些API入口,让读者理清这些API错综复杂的关系。

几个protocol

讲自定义转场就离不开这几个protocol:

UIViewControllerContextTransitioning
UIViewControllerAnimatedTransitioning
UIViewControllerInteractiveTransitioning
UIViewControllerTransitioningDelegate
UINavigationControllerDelegate
UITabBarControllerDelegate

乍一看很多,其实很简单,我们可以将其分为三类:

  1. 描述ViewController转场的:
    UIViewControllerTransitioningDelegate,UINavigationControllerDelegate,UITabBarControllerDelegate
  2. 定义动画内容的
    UIViewControllerAnimatedTransitioning,UIViewControllerInteractiveTransitioning
  3. 表示动画上下文的
    UIViewControllerContextTransitioning

描述ViewController转场的

细说之前先扯个蛋:

为什么苹果要引入这一套API?因为在iOS7之前,做转场动画很麻烦,要写一大堆代码在ViewController中。引入这一套API之后,在丰富功能的同时极大程度地降低了代码耦合,实现方式就是将之前在ViewController里面的代码通过protocol分离了出来。

顺着这个思路往下想,实现自定义转场动画首先需要找到ViewController的delegate。苹果告诉我们切换ViewController有三种形式:UITabBarController内部切换,UINavigationController切换,present modal ViewController。这三种方式是不是需要不同的protocol呢?

我们分别来看下:

  • UIViewControllerTransitioningDelegate自定义模态转场动画时使用。
    设置UIViewController的属性transitioningDelegate。
    @property (nullable, nonatomic, weak) id <UIViewControllerTransitioningDelegate> transitioningDelegate
  • UINavigationControllerDelegate自定义navigation转场动画时使用。
    设置UINavigationController的属性delegate
    @property(nullable, nonatomic, weak) id<UINavigationControllerDelegate> delegate
  • UITabBarControllerDelegate自定义tab转场动画时使用。
    设置UITabBarController的属性delegate
    @property(nullable, nonatomic,weak) id<UITabBarControllerDelegate> delegate

实际上这三个protocol干的事情是一样的,就是我们“扯淡”的内容,只不过他们的应用场景不同罢了。我们下面以UINavigationControllerDelegate为例,其他的类似。

定义动画内容的

UINavigationControllerDelegate主要包含这两个方法:

- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0); - (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
animationControllerForOperation:(UINavigationControllerOperation)operation
fromViewController:(UIViewController *)fromVC
toViewController:(UIViewController *)toVC NS_AVAILABLE_IOS(7_0);

两个方法分别返回UIViewControllerInteractiveTransitioningUIViewControllerAnimatedTransitioning,它们的任务是描述动画行为(转场动画如何执行,就看它俩的)。
从名字可以看出,这两个protocol的区别在于是否是interactive的。如何理解?****interactive动画可以根据输入信息的变化改变动画的进程。****例如iOS系统为UINavigationController提供的默认右滑退出手势就是一个interactive 动画,整个动画的进程由用户手指的移动距离控制。

我们来看下相对简单的UIViewControllerAnimatedTransitioning:

- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
@optional
- (void)animationEnded:(BOOL) transitionCompleted;

transitionDuration返回动画的执行时间,animateTransition处理具体的动画,animationEnded是optional,大部分情况下不需要处理。
这里出现了我们要讲的最后一个protocol:UIViewControllerContextTransitioning

表示动画上下文的

UIViewControllerContextTransitioning也是唯一一个不需要我们实现的protocol。

Do not adopt this protocol in your own classes, nor should you directly create objects that adopt this protocol.

UIViewControllerContextTransitioning提供了一系列方法,为interactive和非interactive动画提供上下文:

//转场动画发生在该View中
- (nullable UIView *)containerView;
//上报动画执行完毕
- (void)completeTransition:(BOOL)didComplete;
//根据key返回一个ViewController。我们通过UITransitionContextFromViewControllerKey找到将被替换掉的ViewController,通过UITransitionContextToViewControllerKey找到将要显示的ViewController
- (nullable __kindof UIViewController *)viewControllerForKey:(NSString *)key;

还有一些其他的方法,我们以后用到再说。

下面我们通过一个简单的Demo串联理解下。

DEMO

 
transitionDemo.gif

这是一个缩放同时修改透明度的动画,我们来看下如何实现。
在上面的讲解中,我们通过倒推的方式来理解转场动画中用到的protocol,在Demo 中,我们会从创建动画开始。

第一步:创建动画

由上面的解析得知,动画是在UIViewControllerAnimatedTransitioning中定义的,所以我们首先创建实现UIViewControllerAnimatedTransitioning的对象:JLScaleTransition

JLScaleTransition.h

@interface JLScaleTransition : NSObject<UIViewControllerAnimatedTransitioning>
@end
JLScaleTransition.m @implementation JLScaleTransition - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext
{
return 0.5f;
} - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *toVC = (UIViewController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = (UIViewController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView * containerView = [transitionContext containerView];
UIView * fromView = fromVC.view;
UIView * toView = toVC.view; [containerView addSubview:toView]; [[transitionContext containerView] bringSubviewToFront:fromView]; NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration animations:^{
fromView.alpha = 0.0;
fromView.transform = CGAffineTransformMakeScale(0.2, 0.2);
toView.alpha = 1.0;
} completion:^(BOOL finished) {
fromView.transform = CGAffineTransformMakeScale(1, 1);
[transitionContext completeTransition:YES];
}];
}
在animateTransition中,我们分别获取两个ViewController的view,将toView添加到containerView中,然后执行动画。为了理解containerView和fromView,toView的关系,我们添加几个log来分析一下: - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *toVC = (UIViewController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = (UIViewController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIView * containerView = [transitionContext containerView];
UIView * fromView = fromVC.view;
UIView * toView = toVC.view;
NSLog(@"startAnimation! fromView = %@", fromView);
NSLog(@"startAnimation! toView = %@", toView);
for(UIView * view in containerView.subviews){
NSLog(@"startAnimation! list container subviews: %@", view);
} [containerView addSubview:toView]; [[transitionContext containerView] bringSubviewToFront:fromView]; NSTimeInterval duration = [self transitionDuration:transitionContext];
[UIView animateWithDuration:duration animations:^{
fromView.alpha = 0.0;
fromView.transform = CGAffineTransformMakeScale(0.2, 0.2);
toView.alpha = 1.0;
} completion:^(BOOL finished) {
fromView.transform = CGAffineTransformMakeScale(1, 1);
[transitionContext completeTransition:YES];
for(UIView * view in containerView.subviews){
NSLog(@"endAnimation! list container subviews: %@", view);
}
}];
}
运行log如下:
2016-06-29 13:50:48.512 JLTransition[1970:177922] startAnimation! fromView = <UIView: 0x7aaef4e0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7aaef5a0>>
2016-06-29 13:50:48.513 JLTransition[1970:177922] startAnimation! toView = <UIView: 0x796ac5a0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x796ac050>>
2016-06-29 13:50:48.513 JLTransition[1970:177922] startAnimation! list container subviews: <UIView: 0x7aaef4e0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7aaef5a0>>
2016-06-29 13:50:49.017 JLTransition[1970:177922] endAnimation! list container subviews: <UIView: 0x796ac5a0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x796ac050>>

可见,转场执行的时候,containerView中只包含fromView,转场动画执行完毕之后,containerView会将fromView移除。因为containerView不负责toView的添加,所以我们需要主动将toView添加到containerView中。

注意!非interactive转场中,动画结束之后需要执行[transitionContext completeTransition:YES];(如果动画被取消,传NO);但是在interactive转场中,动画是否结束是由外界控制的(用户行为或者特定函数),需要在外部调用。

第二步:定义转场

在第二部,我们需要实现UIViewControllerAnimatedTransitioning,并将第一步创建的JLScaleTransition对象返回。

JLScaleNavControlDelegate.h

@interface JLScaleNavControlDelegate : NSObject<UINavigationControllerDelegate>
@end

JLScaleNavControlDelegate.m

@implementation JLScaleNavControlDelegate
- (nullable id <UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
return [JLScaleTransition new];
}
@end

这一步很简单,实现UIViewControllerAnimatedTransitioning对应方法即可。

第三步:设置转场

设置转场其实就是设置delegate(还记得我们“扯淡”的内容吧)。

   self.navigationController.delegate = id<UINavigationControllerDelegate>
self.transitioningDelegate = id<UIViewControllerTransitioningDelegate>
self.tabBarController.delegate = id<UITabBarControllerDelegate>

设置delegate有两种方式:通过代码;通过StoryBoard。

通过代码设置

@interface ViewController ()
@property (nonatomic, strong) JLScaleNavControlDelegate * scaleNavDelegate;
@end @implementation ViewController - (void)viewDidLoad {
[super viewDidLoad];
self.scaleNavDelegate = [JLScaleNavControlDelegate new];
} - (IBAction)triggerTransitionDelegate:(id)sender
{
self.navigationController.delegate = self.scaleNavDelegate;
[self.navigationController pushViewController:[TargetViewController new] animated:YES];
}

链接:https://www.jianshu.com/p/e7155f938e59

如果需要更多效果的话,参考https://github.com/alanwangmodify/WXSTransition

转场动画UINavigationControllerDelegate的更多相关文章

  1. iOS 开发--转场动画

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

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

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

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

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

  4. 转场动画2-Pop动画

    上一篇试讲push动画,这篇分解pop动画 里面关于矩阵有不懂得,参考CATransform3D 特效详解 上图(虚拟机下,图是渣渣 ) 代码直接上 // // PopTransition.h // ...

  5. 转场动画1-Push 动画

    先上效果图: 这篇文章完全是为造轮子制作:原作者是码农界的吴彦祖 作者视频下载地址 好的,我梳理一下思路: 理清思路 ||转场动画可以理解为一个对象,在这个对象里封装了一个动画.具体的我们跟着代码走 ...

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

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

  7. iOS CATransition 自定义转场动画

    https://www.jianshu.com/p/39c051cfe7dd CATransition CATransition 是CAAnimation的子类(如下图所示),用于控制器和控制器之间的 ...

  8. iOS 动画学习之视图控制器转场动画

    一.概述 1.系统会创建一个转场相关的上下文对象,传递到动画执行器的animateTransition:和transitionDuration:方法,同样,也会传递到交互Controller的star ...

  9. OC转场动画UIViewControllerTransitioningDelegate

    该项目一共两个界面,第一个的只有一个SystemAnimationViewController只有UICollecitonView,第二个界面ImgDetailViewController只有一个UI ...

随机推荐

  1. Java Socket 死循环while如何判断客户端断开

    多线程的服务器程序 线程中等待客户端的消息    我的代码能实现服务器与客户端的通信    问题是: 当客户端中断或退出  以上代码却不能判断Socket中断 跳不出while的无限循环 解决方法: ...

  2. [Bayes] KL Divergence & Evidence Lower Bound

    L lower是什么? L lower, 既然大于,那么多出来的这部分是什么?如下推导: 得出了KL的概念,同时也自然地引出了latent variable q.

  3. 解决Error:All flavors must now belong to a named flavor dimension. Learn more at https://d.android.com

    主app的build.gradle里面的 defaultConfig { targetSdkVersion:*** minSdkVersion :*** versionCode:*** version ...

  4. 关于ie6出现的问题的原因归结

    关于ie6出现的问题主要可以归结为以下几种情况把. 当然还存在各种原因,bug的情况也还有各种各样,我只是小结一下我自己经常遇到,比较有代表性的问题.会持续的更新. 1.浏览器本身存在的缺陷 比如: ...

  5. C# MVC+EF—页面搭建

    上一篇文章搭建了基本结构,现在来搭建页面 一.新建控制器 Controllers=>添加=>控制器=>MVC 5控制器=>命名为DepartmentController pub ...

  6. Google、微软、Linkedln、Uber、亚马逊等15+海外技术专家聚首2018TOP100Summit

    11月30日-12月3日,由msup主办的第七届全球软件案例研究峰会(以下简称为TOP100Summit)将在北京国家会议中心举办.本届峰会以“释放AI生产力,让组织向智能化演进”作为开幕式主题, 4 ...

  7. 非节点主机通过内网远程管理docker swarm集群

    这是今天使用 docker swarm 遇到的一个问题,终于在睡觉前解决了,在这篇随笔中记录一下. 在 docker swarm 集群的 manager 节点上用 docker cli 命令可以正常管 ...

  8. sql里的ROW_NUMBER() OVER是啥意思?

    是一个分析函数,生成一个排序列select row_number(XX) over(partition by XXX order by XX [desc/asc]) frou table;partit ...

  9. Django url配置 正则表达式详解 分组命名匹配 命名URL 别名 和URL反向解析 命名空间模式

    Django基础二之URL路由系统 本节目录 一 URL配置 二 正则表达式详解 三 分组命名匹配 四 命名URL(别名)和URL反向解析 五 命名空间模式 一 URL配置 Django 1.11版本 ...

  10. IAR 路径导致的错误

    Error while running "c:\ti\simplelink_cc2640r2_sdk_1_50_00_58\..\xdctools_3_50_03_33_core\xs&qu ...