原文地址:http://blog.csdn.net/jymn_chen/article/details/18944323
iOS 7新出了一个非常好玩的特性,就是View Controllers之间的切换动画,开发者也可以在程序中自行定制切换动画,例如翻转视图、缩放视图、旋转视图等等。另一方面,对于iPhone的使用来说,通过pan手势pop回到之前的视图,使用起来无疑是非常方便的。最开始使用这个特性时感觉非常惊喜。
本文就来说说如何定制我们自己的动画切换方案。
首先定义一个实现UINavigationControllerDelegate协议的类NavigationPerfomer。
故事板非常简单,就是一个UINavigationController和两个View Controller。但是要在UINavigationController中加入一个Object(这个貌似是Xcode 5的新特性吧,以前比较少用故事板,所以不是很确定),该对象的类为NavigaitonPerfomer类:
随后要为NavigationPerformer添加一个outlet:当前的UINavigatinController。并将UINavigationController的委托设为NavigationPerformer类。
故事板中其他的View Controller一切照旧,这里做得非常好,在提供了新的动画接口之后,在UINavigationController类中实现这些接口并不会影响到其他的View Controller的内容。
接着定义两个动画类:PushAnimation和PopAnimation,分别对应Push和Pop动画。
代码如下:
PushAnimtaion类:
- #import <Foundation/Foundation.h>
- @interface PushAnimation : NSObject <UIViewControllerAnimatedTransitioning>
- @end
- #pragma mark - UIViewControllerAnimatedTransitioning Delegate
- /* 动画持续的时间 */
- - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
- return 1;
- }
- /* 动画的内容 */
- - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
- UIViewController *desController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
- UIViewController *srcController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
- [[transitionContext containerView] addSubview:desController.view];
- desController.view.alpha = 0.0;
- [UIView animateWithDuration:[self transitionDuration:transitionContext]
- animations:^{
- srcController.view.transform = CGAffineTransformMakeTranslation(-srcController.view.bounds.size.width, 0.0);
- desController.view.alpha = 1.0;
- }
- completion:^(BOOL finished) {
- srcController.view.transform = CGAffineTransformIdentity;
- [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
- }];
- }
动画类必须实现 UIViewControllerAnimatedTransitioning协议。
协议中的两个必选方法:
- // 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.
- - (NSTimeInterval)transitionDuration:(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;
第一个方法指定动画持续的时间,以秒为单位。
第二个方法定制动画的内容。通过transitionContext这个上下文环境可以获取源视图控制器,目标视图控制器:
- UIViewController *desController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
- UIViewController *srcController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
在进行动画前,先要将desController的视图添加到当前容器视图中。
- + (void)animateWithDuration:(NSTimeInterval)duration animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(4_0); // delay = 0.0, options = 0
该方法才是真正定义动画内容的关键,duration参数指定动画持续的时间,animations代码块给出要执行的变换,当动画完成后调用completion代码块通知transition context,在这里可以进行如设置视图的alpha、还原仿射变换等动作。
这里Push Animation的动画方案是将srcViewController自左向右划出窗口。
另外一个是PopAnimation类:
- @interface PopAnimation : NSObject <UIViewControllerAnimatedTransitioning>
- @end
- #pragma mark - UIViewControllerAnimatedTransitioning Delegate
- - (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
- return 1;
- }
- - (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
- UIViewController *desController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
- UIViewController *srcController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
- [[transitionContext containerView] addSubview:desController.view];
- desController.view.alpha = 0.0;
- [UIView animateWithDuration:[self transitionDuration:transitionContext]
- animations:^{
- srcController.view.transform = CGAffineTransformMakeScale(-1.0, 1.0);
- desController.view.alpha = 1.0;
- }
- completion:^(BOOL finished) {
- srcController.view.transform = CGAffineTransformIdentity;
- [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
- }];
- }
- @end
这里pop的动画方案是翻转srcController的视图。
接着是关键的NavigationPerformer类:
- #import <Foundation/Foundation.h>
- @class PushAnimation;
- @class PopAnimation;
- @interface NavigationPerfomer : NSObject <UINavigationControllerDelegate>
- @property (weak, nonatomic) IBOutlet UINavigationController *navigationController;
- @property (strong, nonatomic) PushAnimation *pushAnimation;
- @property (strong, nonatomic) PopAnimation *popAnimation;
- @property (strong, nonatomic) UIPercentDrivenInteractiveTransition *interactionController;
- @end
注意,interactionController类非常有用,只有通过该类才能使视图响应用户的手势输入。
首先看看UINavigationControllerDelegate的方法:
- #pragma mark - UINavigationController Delegate
- - (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
- animationControllerForOperation:(UINavigationControllerOperation)operation
- fromViewController:(UIViewController *)fromVC
- toViewController:(UIViewController *)toVC
- {
- if (operation == UINavigationControllerOperationPush) {
- return self.pushAnimation;
- }
- else if (operation == UINavigationControllerOperationPop) {
- return self.popAnimation;
- }
- return nil;
- }
- - (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController {
- return self.interactionController;
- }
第一个方法指定不同的动作下的动画方案,在返回对象时,这些对象必须实现UIViewControllerAnimatedTransitioning协议。
第二个方法指定提供交互性的控制器对象,如果返回nil,那么视图将无法响应用户的手势,如滑动等。
下面为导航控制器的视图添加一个Pan手势:
- - (void)awakeFromNib {
- // 在导航控制器的视图上添加pan手势
- UIPanGestureRecognizer *panGesture = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(pan:)];
- [self.navigationController.view addGestureRecognizer:panGesture];
- // 初始化动画方案
- self.pushAnimation = [[PushAnimation alloc] init];
- self.popAnimation = [[PopAnimation alloc] init];
- }
- - (void)pan:(UIPanGestureRecognizer *)pan {
- UIView *view = self.navigationController.view;
- if (pan.state == UIGestureRecognizerStateBegan) { // 只有从左向右滑动的手势才作出响应:pop
- CGPoint location = [pan locationInView:view];
- if (location.x < CGRectGetMidX(view.bounds) && self.navigationController.viewControllers.count > 1) {
- self.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
- [self.navigationController popViewControllerAnimated:YES];
- }
- }
- else if (pan.state == UIGestureRecognizerStateChanged) { // 以当前的位移作为进度执行动画
- CGPoint translation = [pan translationInView:view];
- CGFloat progress = fabs(translation.x / CGRectGetWidth(view.bounds));
- [self.interactionController updateInteractiveTransition:progress];
- }
- else if (pan.state == UIGestureRecognizerStateEnded) {
- if ([pan velocityInView:view].x > 0) {
- [self.interactionController finishInteractiveTransition];
- }
- else { // 如果手势的终点相对于起点在x正向上的位移小于0,即为取消手势
- [self.interactionController cancelInteractiveTransition];
- }
- self.interactionController = nil; // 最后必须将interactionController清空,确保不会影响到后面的动画执行
- }
- }
在pan:方法的执行过程中,程序会根据手势位移的百分比作为进度播放动画。
在切换动画完毕后,一定要将interactionController设为nil,因为某些动画是不需要手势交互的,这样会带来奇怪的行为。
最后是一些运行结果:
Push时的效果:
使用Pan手势Pop时的效果:
通过这个新的Transition接口我们可以实现一些比较好玩的动画,Demo已经上传,有兴趣的可以下载看看。
这部分还是挺好玩的,Just enjoy.
参考资料:
objc.io #5 View Controller Transitions
objcio / issue5-view-controller-transitions