UIViewController转场的原理
UIViewController是利用UIViewControllerAnimatedTransitioning协议把两个ViewController的View放在一个容器Container里进行UIView动画
补充:
- 把UIView放入一个容器是通过UIViewControllerContextTransitioning获取到画布数据达到的
- 所谓画布数据就是元数据
UIViewController转场用到的协议
转场动画实现需要用到2个协议
- UIViewControllerAnimatedTransitioning(需要自己实现)
- UIViewControllerContextTransitioning(仅需要使用)
转场动画调用需要1个协议,三选一
- UIViewControllerTransitioningDelegate(present出VC用)
- UINavigationControllerDelegate(push出VC用)
- UITabBarControllerDelegate(switch切换VC用)
出现VC的方法
根据VC出现的方法不同,需要选用不同的代理(3选1)来调用动画,常见的VC切换方法无非就是Present,NavigationPush,TabBarSwitch三种方式.三种方式的代理中,都通过返回一个遵循了UIViewControllerAnimatedTransitioning协议的指针来调用动画.
我们暂且称遵循了UIViewControllerAnimatedTransitioning协议的Object为动画控制器(AnimationController).
Present出窗口
@interface ViewController () <UIViewControllerTransitioningDelegate>
@end
- (void)presentViewController {
ViewController *next = [[ViewController alloc]init];
next.transitioningDelegate = self;
[self presentViewController:next animated:YES completion:nil];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return AnimationController;
}
Navigation中Push窗口
@interface ViewController () <UINavigationControllerDelegate>
@end
- (void)pushViewController {
self.navigationController.delegate = self;
ViewController *next = [[ViewController alloc]init];
[self.navigationController pushViewController:next animated:YES];
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return AnimationController;
}
Tabbar中Switch窗口
@interface ViewController () <UITabBarControllerDelegate>
@end
- (void)switchViewController {
self.tabBarController.delegate = self;
}
- (id<UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController animationControllerForTransitionFromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return AnimationController;
}
AnimationController是什么
AnimationController是任何一个实现了UIViewControllerAnimatedTransitioning代理方法的类
可以充当AnimationController的候选:
- 独立的AnimationController的Object(!!推荐这个方法)
- 某个ViewController本身
- 在一个NavigationController的栈中Navgation自己本身
- TabBar中TabBar本身
一般都使用第一种,2/3/4仅做讨论研究
独立的AnimationController类
为了防止出现各种意外情况,大多数都使用新建一个继承于NSObject的Class,然后用它来完成AnimationController
// AnimationController.h
#import <Foundation/Foundation.h>
#import <UIKit/UIKit.h>
@interface AnimationController : NSObject <UIViewControllerAnimatedTransitioning>
@end
// AnimationController.m
#import "AnimationController.h"
@implementation AnimationController
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
return 2.0f;//动画时间
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
//动画效果
.......
}
@end
此时加载头文件,然后返回一个实例化的对象即可
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return [[AnimationController alloc]init];
}
关于AnimationController更多的讨论可以看文章末尾的[谁可以当AnimationController](## 扩展研究)
动画效果的控制ControllerContext
在完成AnimationController的构造之后,动画效果的具体实现是通过函数
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext;
完成的.
这里我们回顾开篇的话UIViewController是利用UIViewControllerAnimatedTransitioning协议把两个ViewController的View放在一个容器Container里进行UIView动画,具体怎么实现呢?可以看到函数的入参类型是一个遵循UIViewControllerContextTransitioning协议的Object,我们打开这个协议的头文件
UIKIT_EXTERN NSString *const UITransitionContextFromViewControllerKey NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UITransitionContextToViewControllerKey NS_AVAILABLE_IOS(7_0);
UIKIT_EXTERN NSString *const UITransitionContextFromViewKey NS_AVAILABLE_IOS(8_0);
UIKIT_EXTERN NSString *const UITransitionContextToViewKey NS_AVAILABLE_IOS(8_0);
开头迎面而来的是4个Key,这4个Key是用来取转场中两个ViewController或者他们的View的,我们拿个简单的效果举例
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
//1. 通过viewControllerForKey函数和Key值得到toViewController
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//2. 通过finalFrameForViewController函数获得到动画结束后toViewController的Frame
CGRect finalRect = [transitionContext finalFrameForViewController:toVC];
//3. 把toViewController的View加入到需要做动画的容器中,容器通过containerView函数获取
[[transitionContext containerView]addSubview:toVC.view];
//4. 动画开始的状态
toVC.view.frame = CGRectOffset(finalRect, 0, [[UIScreen mainScreen]bounds].size.height);
//5. 使用UIView层面上的动画,让toViewController.view和fromViewController.view做动画
//这里基本就到本质了,实质上是通过Context画布(元数据),让还没有DidAppear的ViewController的View传递过来,然后做两个View层上的动画
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
//6. 动画结束的状态
toVC.view.frame = finalRect;
} completion:^(BOOL finished) {
//7. 重要!!这里一定要声明转场动画已经结束了!!
[transitionContext completeTransition:YES];
}];
}
为什么要completeTransition
因为在转场动画进行的过程中,为了防止动画终端,此时用的是画布Context生成的View,整个过程中,App是无法进行响应Respond的,所以如果不进行completeTransition的声明,整个程序就无法响应任何事件,动画结束就进入卡死状态了.
总结
当我们需要转场时,需要先找到一个遵循了Present/Navigation/TabBar对应协议的转场动画协议进行实现,然后返回一个遵循了UIViewControllerAnimatedTransitioning的AnimationController,在AnimationController实现的协议中,使用UIViewControllerContextTransitioning协议对动画进行具体实现
扩展研究
TabBar的另外一种转场动画
TabBar除了使用AnimationController实现转场之外,还可以使用另外一种方式进行动画转场,看TabBarController的头文件中
NS_CLASS_AVAILABLE_IOS(2_0) @interface UITabBarController : UIViewController <UITabBarDelegate, NSCoding>
@property(nonatomic,readonly) UITabBar *tabBar NS_AVAILABLE_IOS(3_0);
其本身是遵循UITabBarDelegate协议的,并且具有tabBar变量.那么我们可以实现UITabBarDelegate协议中的
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item;
// called when a new view is selected by the user (but not programatically)
这个函数,来拦截转换的过程并加入Layer层动画
- (void)tabBar:(UITabBar *)tabBar didSelectItem:(UITabBarItem *)item {
CATransition* animation = [CATransition animation];
[animation setDuration:0.5f];
[animation setType:kCATransitionFade];
[animation setSubtype:kCATransitionFromRight];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut]];
[[self.view layer]addAnimation:animation forKey:@"switchView"];
}
在该函数和AnimationController动画同时存在时,会优先响应该函数,不执行AnimationController,所以在改动旧代码的时候需要检查一下,不然会造成新加入的动画无效.
谁可以当AnimationController(可略过)
某个ViewController本身
如果仅仅是在某个ViewController中Present出另一个,可以把其本身当作AnimationController.
@interface ViewController () <UIViewControllerTransitioningDelegate, UIViewControllerAnimatedTransitioning>
@end
@implementation ViewController
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return self;
}
@end
使用Navigation本身
在使用了Navigation的情况下,可以使用某一个栈中的ViewController作为AnimationController,但是不能使用Navigation本身.
这种情形下可以
@interface ViewController () <UINavigationControllerDelegate, UIViewControllerAnimatedTransitioning>
@end
@implementation ViewController
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return self;
}
@end
这种情形下不行,原因可能是由于self.navigationController.delegate = self相当于NavigationController中self.delegate = self,原因不确定,总之会Crash
//ViewController.h
@interface ViewController () <UINavigationControllerDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.navigationController.delegate = self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return (NavViewController *)self.navigationController;
}
@end
//一个自定义的继承于UINavigationController的Navigation
@interface NavViewController : UINavigationController<UIViewControllerAnimatedTransitioning>
@end
使用TabBar本身
与Navigation不同的是,如果才用TabBar本身作为AnimationController就不会Crash
//ViewController.h
@interface ViewController () <UITabBarControllerDelegate>
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.tabBarController.delegate = self;
}
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return (TabViewController *)self.tabBarController;
}
@end
//一个自定义的继承于UITabBarController的TabBar
@interface TabViewController : UITabBarController <UIViewControllerAnimatedTransitioning>
@end
而且如果始终TabBar中某一个ViewController作为AnimationController自然也可以.
@interface ViewController () <UITabBarControllerDelegate, UIViewControllerAnimatedTransitioning>
@end
@implementation ViewController
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC {
return self;
}
@end
参考文献
[1]http://kittenyang.com/uiviewcontrollertransitioning/
[2]http://objccn.io/issue-12-3/