iOS7之前,ViewController切换主要有4种方式:
1.Push/Pop NavigationViewController
2.Present and dismis Modal
3.UITabBarController
4.addChildViewController
iOS5添加函数:
- (void)transitionFromViewController:(UIViewController *)fromViewController toViewController:(UIViewController *)toViewController duration:(NSTimeInterval)duration options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^)(BOOL finished))completion NS_AVAILABLE_IOS(5_0);
iOS7添加新转场方式:
1.非交互式切换,即定义一种从一个VC到另一个VC的动画效果,切换的时候自动播放
2.交互式切换,这种方式同样需要定义动画效果,只是这个动画效果会根据跟随交互式手势来切换VC并同时播放动画效果
关键API:
1.动画控制器(AnimationControllers)
遵循UIViewControllerAnimatedTransitioning协议,负责实际执行动画,切换的具体内容,也即“切换中应该发生什么”
2.交互控制器(InteractionControllers)
遵循UIViewControllerInteractiveTransitioning协议,负责控制可交互式的转场,用一个百分比来控制交互式切换的过程
常用方法:
-(void)updateInteractiveTransition:(CGFloat)percentComplete 切换更新视图百分比,一般通过手势识别的长度之类的来计算一个值,然后进行更新。之后的例子里会看到详细的用法
-(void)cancelInteractiveTransition 报告交互取消,返回切换前的状态
-(void)finishInteractiveTransition 报告交互完成,更新到切换后的状态
3.转场代理控制器(TransitioningDelegatesControllers)
遵循UIViewControllerTransitioningDelegate协议,负责提供动画控制器和交互控制器
4.转场上下文(TransitioningContexts)
遵循UIViewControllerContextTransitioning 协议,负责定义转场需要的元数据(转场过程中所参与的视图控制器与视图)
5.转场协调器(TransitionCoordinators)
遵循UIViewControllerTransitionCoordinator 协议,负责处理运行转场动画时,并行运行其他动画
····苹果给我们开发者提供的是都是协议接口,所以我们能够很好的单独提出来写成一个个类,在里面实现我们各种自定义效果
1.非交互式转场实现范例:
1.自定义动画控制器,遵循UIViewControllerAnimatedTransitioning协议
@interface MyAnimator : NSObject<UIViewControllerAnimatedTransitioning>
@end
#import "MyAnimator.h"
@implementation MyAnimator
#pragma mark------------------------ UIViewControllerAnimatedTransitioning
//该方法需要自己根据视图切换上下文transitionContext参数,返回切换所需时间
-(NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext{
return 1.0;
}
//完成容器转场动画的主要方法,我们对于切换时的UIView的设置和动画都在这个方法中完成,转场的元数据从切换上下文transitionContext参数对象获取
-(void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
//从上下文对象拿到前后两视图
UIViewController *toViewController=[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromViewController=[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//拿到后视图的界面view进行属性设置
UIView *toView=toViewController.view;
toView.alpha=0.0;
//添加后视图的界面view到上下文容器中
[[transitionContext containerView]addSubview:toView];
//创建动画
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//动画效果有很多,这里就展示个左偏移
fromViewController.view.transform = CGAffineTransformMakeTranslation(-[UIScreen mainScreen].bounds.size.width, 0);
toViewController.view.alpha = 1.0;
} completion:^(BOOL finished) {
fromViewController.view.transform = CGAffineTransformIdentity;
//声明过渡结束-->一定要在过渡结束时调用completeTransition:方法
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
}];
}
2.确定转场代理控制器
代理导航栏转场的需要遵循UINavigationControllerDelegate;
代理模态视图转场的需要遵循UIViewControllerTransitioningDelegate
@interface FirstViewController : UIViewController<UINavigationControllerDelegate,UIViewControllerTransitioningDelegate>
@end
#import "FirstViewController.h"
#import "SecondViewController.h"
#import "MyAnimator.h"
//自身为转场代理控制器
@implementation FirstViewController{
SecondViewController *secondVC;
MyAnimator *myAnimator;//动画控制器
}
-(void)viewDidLoad{
[super viewDidLoad];
myAnimator=[[MyAnimator alloc]init];
secondVC=[[SecondViewController alloc]init];
//设置后视图模态转场代理控制器为自己
secondVC.transitioningDelegate=self;
//设置导航控制器代理为自己
self.navigationController.delegate=self;
}
-(IBAction)pushAction:(id)sender {
//非交互的转场,指的是完全按照系统指定的切换机制,用户无法中途取消或者控制进度切换.
[self.navigationController pushViewController:secondVC animated:YES];
}
-(IBAction)presentAction:(id)sender {
[self presentViewController:secondVC animated:YES completion:nil];
}
3.实现转场代理控制器的协议方法
如果既是导航栏转场代理又是模态视图转场代理,那么需要两套代理方法都实现
两套协议方法中每一套都分为两类型,分别是交互式需要实现的协议方法与非交互式需要实现的协议方法
#pragma mark - UINavigationControllerDelegate
//非交互
//指定转场动画控制器,返回nil会使用系统默认动画控制器
-(id<UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
if (operation == UINavigationControllerOperationPush) {//指定那种切换类型
return myAnimator;
}else{
return nil;
}
}
#pragma mark - TransitioningDelegate (Modal)
// 非交互
//指定模态跳转使用的动画控制器
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
return myAnimator;
}
//指定模态返回使用的动画控制器,返回nil会默认使用系统的动画控制器
-(id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
return nil;
}
2.交互式转场实现范例:
1.自定义动画控制器,遵循UIViewControllerAnimatedTransitioning协议
2.确定转场代理控制器
//为该转场代理控制器添加交互控制器成员变量(手势交互以及协议方法中需要用到)
UIPercentDrivenInteractiveTransition *interactionController;//交互控制器
//为该转场代理控制器添加手势交互
UIPanGestureRecognizer *panRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(didClickPanGestureRecognizer:)];
[self.navigationController.view addGestureRecognizer:panRecognizer];
//实现手势交互方法
- (void)didClickPanGestureRecognizer:(UIPanGestureRecognizer*)recognizer{
UIView* view = self.view;
if (recognizer.state == UIGestureRecognizerStateBegan) {
// 获取手势的触摸点坐标
CGPoint location = [recognizer locationInView:view];
// 判断,用户从右半边滑动的时候,推出下一个VC(根据实际需要是推进还是推出)
if (location.x > CGRectGetMidX(view.bounds) && self.navigationController.viewControllers.count == 1){
interactionController= [[UIPercentDrivenInteractiveTransition alloc] init];
//[self.navigationController pushViewController:secondVC animated:YES];
[self presentViewController:secondVC animated:YES completion:nil];
}
} else if (recognizer.state == UIGestureRecognizerStateChanged) {
// 获取手势在视图上偏移的坐标
CGPoint translation = [recognizer translationInView:view];
// 根据手指拖动的距离计算一个百分比,切换的动画效果也随着这个百分比来走
CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds));
// 交互控制器控制动画的进度
[interactionController updateInteractiveTransition:distance];
} else if (recognizer.state == UIGestureRecognizerStateEnded) {
CGPoint translation = [recognizer translationInView:view];
// 根据手指拖动的距离计算一个百分比,切换的动画效果也随着这个百分比来走
CGFloat distance = fabs(translation.x / CGRectGetWidth(view.bounds));
// 移动超过一半就强制完成
if (distance > 0.5) {
[interactionController finishInteractiveTransition];
} else {
[interactionController cancelInteractiveTransition];
}
// 结束后一定要置为nil
interactionController = nil;
}
}
3.实现转场代理控制器的协议方法
#pragma mark - UINavigationControllerDelegate
//交互
-(id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController*)navigationController interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController{
return interactionController;
}
#pragma mark - TransitioningDelegate (Modal)
//交互
-(id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator{
return interactionController;
}
-(id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator{
return nil;
}
//继承UINavigationController编写自定义NavigationController,实现手势pop上一个ViewController缩放效果
#import <UIKit/UIKit.h>
@interface MyNavigationController : UINavigationController <UIGestureRecognizerDelegate>
@end
#import "MyNavigationController.h"
#import <QuartzCore/QuartzCore.h>
@interface MyNavigationController (){
CGPoint startTouch;
UIImageView *lastScreenShotView;
UIView *blackMask;
}
@property (nonatomic,retain) UIView *backgroundView;
@property (nonatomic,retain) NSMutableArray *screenShotsList;
@end
@implementation MyNavigationController
- (void)viewDidLoad{
[super viewDidLoad];
self.interactivePopGestureRecognizer.enabled = NO;//用这种效果就得把系统默认手势pop方式禁用掉
self.screenShotsList = [NSMutableArray array];
UIImage *capturedImage = [self capture];
if (capturedImage) {
[self.screenShotsList addObject:capturedImage];
}
UIPanGestureRecognizer *recognizer = [[UIPanGestureRecognizer alloc]initWithTarget:self action:@selector(paningGestureReceive:)];
recognizer.delegate = self;
[self.view addGestureRecognizer:recognizer];
}
// 重写push
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated{
UIImage *capturedImage = [self capture];//还没push到新controller时先获取当前屏幕快照保留起来
if (capturedImage) {
[self.screenShotsList addObject:capturedImage];
}
[super pushViewController:viewController animated:animated];
}
// 重写pop
- (UIViewController *)popViewControllerAnimated:(BOOL)animated{
[self.screenShotsList removeLastObject];//pop方式返回直接把快照数组最后一张快照删除
return [super popViewControllerAnimated:animated];
}
// 获取屏幕最顶ViewController的View的快照
- (UIImage *)capture{
UIView *topView=[[UIApplication sharedApplication]keyWindow].rootViewController.view;
UIGraphicsBeginImageContextWithOptions(topView.bounds.size, topView.opaque, 0.0);
[topView.layer renderInContext:UIGraphicsGetCurrentContext()];
UIImage * img = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
return img;
}
// 移动的时候设置背景图中的快照图位置和背景图的阴影图透明度
- (void)moveViewWithX:(float)x{
NSLog(@"Move to:%f",x);
x = x>320?320:x;
x = x<0?0:x;
UIView *topView=[[UIApplication sharedApplication]keyWindow].rootViewController.view;
CGRect frame = topView.frame;
frame.origin.x = x;//计算originX
topView.frame = frame;//改变快照原点坐标
float scale = (x/6400)+0.95;//计算缩放比例
float alpha = 0.4 - (x/800);
lastScreenShotView.transform = CGAffineTransformMakeScale(scale, scale);//缩放
blackMask.alpha = alpha;
}
//pan手势回调
- (void)paningGestureReceive:(UIPanGestureRecognizer *)recoginzer{
// 当前是navigation的根视图时不响应
if (self.viewControllers.count <= 1) return;
UIView *keyWindow=[[UIApplication sharedApplication]keyWindow];
UIView *topView=[[UIApplication sharedApplication]keyWindow].rootViewController.view;
//获取手势在整个屏幕的坐标
CGPoint touchPoint = [recoginzer locationInView:keyWindow];
//手势开始的时候
if (recoginzer.state == UIGestureRecognizerStateBegan) {
startTouch = touchPoint;//记录手势开始的坐标
if (!self.backgroundView)
{
CGRect frame = topView.frame;
self.backgroundView = [[[UIView alloc]initWithFrame:CGRectMake(0, 0, frame.size.width , frame.size.height)]autorelease];
self.backgroundView.backgroundColor=[UIColor yellowColor];
//在背景层上添加阴影层
blackMask = [[[UIView alloc]initWithFrame:CGRectMake(0, 0, frame.size.width , frame.size.height)]autorelease];
blackMask.backgroundColor = [UIColor redColor];
[self.backgroundView addSubview:blackMask];
//在topView的父视图中插入backgroundView在topView的下层
[topView.superview insertSubview:self.backgroundView belowSubview:topView];
}
//隐藏插入的背景层
self.backgroundView.hidden = NO;
//把存在的上一次添加的快照图移除
if (lastScreenShotView) {
[lastScreenShotView removeFromSuperview];
}
//拿出navigation的上一个viewcontroller快照图插入到背景图中阴影图的下面
UIImage *lastScreenShot = [self.screenShotsList lastObject];
lastScreenShotView = [[[UIImageView alloc]initWithImage:lastScreenShot]autorelease];
[self.backgroundView insertSubview:lastScreenShotView belowSubview:blackMask];
}else if (recoginzer.state == UIGestureRecognizerStateEnded){//手势结束的时候判断要不要pop
if (touchPoint.x - startTouch.x > 50){//pop
[UIView animateWithDuration:0.3 animations:^{
[self moveViewWithX:320];
} completion:^(BOOL finished) {
[self popViewControllerAnimated:NO];
CGRect frame = topView.frame;
frame.origin.x = 0;
topView.frame = frame;
self.backgroundView.hidden = YES;
}];
}
else{//不pop
[UIView animateWithDuration:0.3 animations:^{
[self moveViewWithX:0];
} completion:^(BOOL finished) {
self.backgroundView.hidden = YES;
}];
}
return;
}else if (recoginzer.state == UIGestureRecognizerStateCancelled){//手势取消的时候不pop
[UIView animateWithDuration:0.3 animations:^{
[self moveViewWithX:0];
} completion:^(BOOL finished) {
self.backgroundView.hidden = YES;
}];
return;
}else if (recoginzer.state == UIGestureRecognizerStateChanged){//剩下手势移动的时候
[self moveViewWithX:touchPoint.x - startTouch.x];
}
}
@end