ios7 push/pop转场动画

前言



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

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

我们本篇文章目标效果:

视图切换种类

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

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

协议



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

我们先来学习UIViewControllerAnimatedTransitioning协议:

@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协议,如下:

//
//  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,第二个参数是用于指定动画时长。

实现文件

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

//
//  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这个代理方法,我们可以通过苹果提供的键来获取对应的控制器:

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才能使用。

我们这里通过:

ViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];

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

然后通过:

DetailController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];

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

然后再通过

UIView *containerView = [transitionContext containerView];

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

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

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;

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

[containerView addSubview:toVC.view];
[containerView addSubview:tempView];

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

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

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

分析pop动画

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

// 第一个view是fromVC.view
// 第二个view是push进来时所生成的toImageView截图
for (UIView *view in containerView.subviews) {
    NSLog(@"%@", view);
    if (fromVC.view == view) {
      NSLog(@"YES");
    }
}

打印结果:

<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放在容器最上层:

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

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

[tempView removeFromSuperview];

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

写在最后



从来没有研究过这方面的知识,最近要做技术分享,正好也好好研究研究,如果写得不好,希望大家不要笑话!!!

其它转场效果,后续会分享出来!!!

其它转场效果,后续会分享出来!!!

其它转场效果,后续会分享出来!!!

源代码



小伙伴们可以到笔者的github下载:https://github.com/CoderJackyHuang/PushPopTransitionDemo

请随手给一个star吧!!!

阅读原文

关注我



微信公众号:iOSDevShares
有问必答QQ群:324400294

时间: 2024-09-12 15:37:34

ios7 push/pop转场动画的相关文章

深入学习iOS7自定义导航转场动画_IOS

在iOS7以前,开发者如果希望定制导航控制器推入推出视图时的转场动画,一般都只能通过子类化UINavigationController或者自己编写动画代码去覆盖相应的方法,现在iOS7为开发者带来了福音,苹果公司引入了大量新API,给予了开发者很高的自由度,在处理由UIViewController管理的UIView动画时,这些API使用方便,可扩展性也很强,定制起来非常轻松:  全新的针对UIView的动画block方法 全新的UIViewControllerAnimatedTransition

iOS 7 present/dismiss自定义转场动画

前言 iOS 7以后提供了自定义转场动画的功能,我们可以通过遵守协议完成自定义转场动画.本篇文章讲解如何实现自定义present.dismiss自定义动画. 关于自定义push/pop转场动画,请阅读iOS 7 push/pop自定义转场动画 效果图 本篇文章实现的动画切换效果图如下: 视图切换种类 如下效果图,这是有两大类视图切换动画的,一种是交互式的,另一种就是自定义的. 本篇只讲其中的UIViewControllerAnimatedTransitioning协议,来实现present.di

控制器转场动画详解

控制器转场动画详解   效果   说明 1. 控制器转场动画包括了普通控制器的present与dismiss转场动画,还有NavigationController的push与pop转场动画.其中,NavigationController的pop动画包含了回退百分比显示 2. 对转场动画对象进行行为抽象,让使用更加简单 3. 即使简化了使用,控制器转场动画也是属于比较难掌握的   源码 https://github.com/YouXianMing/ViewControllersTransition

iOS自定义转场动画实战讲解

转场动画这事,说简单也简单,可以通过presentViewController:animated:completion:和dismissViewControllerAnimated:completion:这一组函数以模态视图的方式展现.隐藏视图.如果用到了navigationController,还可以调用pushViewController:animated:和popViewController这一组函数将新的视图控制器压栈.弹栈. 下图中所有转场动画都是自定义的动画,这些效果如果不用自定义动

IOS实战之自定义转场动画详解_IOS

转场动画这事,说简单也简单,可以通过presentViewController:animated:completion:和dismissViewControllerAnimated:completion:这一组函数以模态视图的方式展现.隐藏视图.如果用到了navigationController,还可以调用pushViewController:animated:和popViewController这一组函数将新的视图控制器压栈.弹栈. 下图中所有转场动画都是自定义的动画,这些效果如果不用自定义动

iOS实现类似格瓦拉电影的转场动画_IOS

用过格瓦拉电影,或者其他app可能都知道,一种点击按钮用放大效果实现转场的动画现在很流行,效果大致如下 自定义转场动画 首先就要声明一个遵守UIViewControllerAnimatedTransitioning协议的类. 然后实现协议中的两个函数 // This is used for percent driven interactive transitions, as well as for container controllers that have companion animati

定制controller转场动画

定制controller转场动画   从iOS7开始就可以自由定制控制器间的转场动画了,以下实例描述最简单的定制方式,达到的效果如下所示: 为了实现这个效果需要这么多的文件-_-!!!! RootViewController // // RootViewController.h // ControllerCustom // // Copyright (c) 2014年 Y.X. All rights reserved. // #import <UIKit/UIKit.h> @interface

iOS之UI--转场动画

1.什么是转场动画?     就是从一个场景转换到另一个场景,像导航控制器的push效果,就是一个转场.      2.如何创建转场动画     创建转场动画     CATransition *anim = [CATransition animation];     设置转场类型     anim.type = @"cube";     anim.duration = 1;     设置转场的方向     anim.subtype = kCATransitionFromLeft;  

core Animation之CATransition(转场动画)

用于做转场动画,能够为层提供移出屏幕和移入屏幕的动画效果.iOS比Mac OS X的转场动画效果少一点 UINavigationController就是通过CATransition实现了将控制器的视图推入屏幕的动画效果 属性解析: type:动画过渡类型 subtype:动画过渡方向 startProgress:动画起点(在整体动画的百分比) endProgress:动画终点(在整体动画的百分比) 代码一: #import "ViewController.h" @interface V