iOS之Custom UIViewController Transition
本文学习下自定义ViewController的切换,从无交互的到交互式切换。
(本文已同步到我的小站:icocoa,欢迎访问。)
iOS7中定义了3个协议:
UIViewControllerTransitioningDelegate:
用于支持自定义切换或切换交互,定义了一组供animator对象实现的协议,来自定义切换。
可以为动画的三个阶段单独提供animator对象:presenting,dismissing,interacting。
UIViewControllerAnimatedTransitioning:
主要用于定义切换时的动画。这个动画的运行时间是固定的,而且无法进行交互。
UIViewControllerInteractiveTransitioning:
负责交互动画的对象。
该对象是通过加快/减慢动画切换的过程,来响应触发事件或者随时间变化的程序输入。对象也可以提高切换的逆过程来响应变化。
比如iOS7上NavController响应手指滑动来切换viewController
如果要提供交互,那么也需要提供实现UIViewControllerAnimatedTransitioning的对象,这个对象可以就是之前实现UIViewControllerInteractiveTransitioning的对象,也可以不是。
如果不需要(动画按预先设置的进行),则可以自己实现。如果要提供交互,那么也需要实现UIViewControllerAnimatedTransitioning。
上述是API文档中的说明,我们按图索骥,根据说明一步一步来实现一个无交互的切换动画。
为了方便,我在一个viewController A里添加按钮,点击后以present modal的方式跳转到viewController B。B中也放置一个按钮,用来回到A。
为了支持自定义transition,iOS7中UIViewController多了transitioningDelegate的属性。这个delegate需要实现相关的protocol,可以是viewcontroller本身。不过,这样的话,很显然不利于自定义部分的重用。因此我们新建一个类:
@interface ZJTransitionDelegateObj : NSObject<UIViewControllerTransitioningDelegate> @end
然后实现delegate,UIViewControllerTransitioningDelegate定义了4个protocol,后2个是用于交互时用的,这里我们只需实现前2个。
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed; - (id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; - (id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;
前2个返回的是实现 UIViewControllerAnimatedTransitioning 协议的对象,这里我们返回self,这样意味着我们的ZJTransitionDelegateObj类还需要实现相应的协议:
// 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; @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;
根据说明,我们可以看到主要是实现第2个协议。transitionContext是一个实现UIViewControllerContextTransitioning协议的对象,再进一步查看该协议,可以看到一系列方法,具体的就不详细展开,看一下代码:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
{
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containView = [transitionContext containerView];
[containView addSubview:toViewController.view]; CGRect rect = toViewController.view.frame;
rect.origin.x = -320;
rect.origin.y = -rect.size.height;
toViewController.view.frame = rect;
[UIView animateKeyframesWithDuration:1.5 delay:0 options:UIViewKeyframeAnimationOptionLayoutSubviews animations:^{
CGRect frame = rect;
frame.origin.x = 0;
frame.origin.y = 0;
toViewController.view.frame = frame;
} completion:^(BOOL finished) { [transitionContext completeTransition:YES];
}]; }
内容很简单,这里需要注意的是 [transitionContext completeTransition:YES] 很重要。如果没有使用,系统会不知道当前的transition是否已经结束,这样造成的后果:使app进入某种未知状态,比如presentingViewController能看到新view但是无法和用户交互。关于这一点,Apple把它放置在头文件里说明了,所以我推荐大家遇到问题的时候,不妨先直接查看头文件中的注释说明(xCode中按住command后鼠标点击类名)。
接下来,看一下app,发现present的方式是以对角的方式出现了。如果你不小心点击了ViewCOntroller B的dismiss按钮,发现之前的view也以同样的方式出现了。这是因为我们尚未做present和dismiss的区分。接下来给ZJTransitionDelegateObj增加增加Bool属性
@interface ZJTransitionDelegateObj ()
@property (nonatomic) BOOL isPresent;
@end
并在协议中赋值:
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
{
self.isPresent = YES;
return self;
} - (id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
{
self.isPresent = NO;
return self;
}
然后修改动画:
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
{
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromViewController = [transitionContext viewControllerForKey: UITransitionContextFromViewControllerKey];
UIView *containView = [transitionContext containerView];
CGRect rect = toViewController.view.frame;
if (self.isPresent)
{
[containView addSubview:toViewController.view]; rect.origin.x = - rect.size.width;
rect.origin.y = - rect.size.height;
toViewController.view.frame = rect;
[UIView animateKeyframesWithDuration:1.5 delay: options:UIViewKeyframeAnimationOptionLayoutSubviews animations:^{
CGRect frame = rect; frame.origin.x = ;
frame.origin.y = ;
toViewController.view.frame = frame;
} completion:^(BOOL finished) { [transitionContext completeTransition:YES];
}]; }
else
{ [containView insertSubview:toViewController.view atIndex:];
rect = fromViewController.view.frame; [UIView animateKeyframesWithDuration:1.5 delay: options:UIViewKeyframeAnimationOptionLayoutSubviews animations:^{
CGRect frame = rect; frame.origin.x = - rect.size.width;
frame.origin.y = - rect.size.height;
fromViewController.view.frame = frame;
} completion:^(BOOL finished)
{
[fromViewController.view removeFromSuperview];
[transitionContext completeTransition:YES];
}]; } }
假设A present B,那么fromViewController和toViewController在present和dismiss是正好相反的,如图:
而且present时,container view中没有subview,需要自己添加B的view。而dismiss的时候,container view中已经添加了B的view,所以要先把A的view添加到最底层,然后对B的view做动画,最后还要把它移除。
这样,一个简单的custom transition 就已经完成了。
下面,我们趁热打铁,来实现一个交互式的custom transion。何谓交互式的custom transion呢?举个简单的例子,有个navController,push了viewController A,在A页面可以通过手指从左向右的滑动的方式pop到上一级ViewController。在滑动的过程中,你也可以取消当前的pop。这种交互的方式,是Apple在iOS7中推荐的。
我们看一下WWDC中的讲义,来领会一下这样的一个过程:
上图就是交互式动画过程中的状态变化,其中更新,结束和取消的几个状态,是需要客户端调用来通知系统的。
根据WWDC的说明,最简单的实现交互式动画的方法就是通过继承 UIPercentDrivenInteractiveTransition。
下面我们尝试实现一个交互式动画,我选择的是对nav的pop添加交互式动画,通过两个手指向内滑动pop当前的viewcontroller。与此同时,点击返回键能正常的pop当前的viewcontroller。
首先根据WWDC的例子,添加一个新类:
#import <UIKit/UIKit.h> @interface ZJSliderTransition : UIPercentDrivenInteractiveTransition
- (instancetype)initWithNavigationController:(UINavigationController *)nc; @property(nonatomic,assign) UINavigationController *parent;
@property(nonatomic,assign,getter = isInteractive) BOOL interactive; @end
注意源文件中需要添加一些变量,并且在初始化的时候添加gesture:
#import "ZJSliderTransition.h" @interface ZJSliderTransition ()
{
CGFloat _startScale;
}
@end @implementation ZJSliderTransition
- (instancetype)initWithNavigationController:(UINavigationController *)nc;
{
if (self = [super init])
{
self.parent = nc; UIPinchGestureRecognizer *pintchGesture = [[UIPinchGestureRecognizer alloc] initWithTarget:self action:@selector(handlePinch:)];
[self.parent.topViewController.view addGestureRecognizer:pintchGesture];
}
return self;
} - (void)handlePinch:(UIPinchGestureRecognizer *)gr {
CGFloat scale = [gr scale];
switch ([gr state]) {
case UIGestureRecognizerStateBegan:
self.interactive = YES; _startScale = scale;
self.parent.delegate = self.parent.topViewController; [self.parent popViewControllerAnimated:YES];
break;
case UIGestureRecognizerStateChanged: {
CGFloat percent = (1.0 - scale/_startScale);
[self updateInteractiveTransition: (percent <= 0.0) ? 0.0 : percent];
break;
}
case UIGestureRecognizerStateEnded:
case UIGestureRecognizerStateCancelled:
if([gr velocity] >= 0.0 || [gr state] == UIGestureRecognizerStateCancelled)
[self cancelInteractiveTransition];
else
[self finishInteractiveTransition];
self.interactive = NO;
break;
default:
break;
}
}
@end
由此可见,gesture的状态和交互式的状态,是一一对应的。因为我们希望添加的动画不影响正常的返回pop,我们在pinch操作开始的时候,再设置navController的delegate。当然,这样的设置有点怪。
接下来,就是添加我们的sliderTransition。为了和其他transition区分,我们给ZJToViewController添加一个BOOL属性:isPopInterActive。
当isPopInterActive为YES的时候,我们才去准备navController的delegate需要实现的相关对象。
ZJViewController类添加的部分:
- (void)viewDidAppear:(BOOL)animated
{
[super viewDidAppear:animated];
if (self.isPopInterActive)
{
_sliderTransition = [[ZJSliderTransition alloc] initWithNavigationController:self.navigationController]; }
}
- (void)viewDidDisappear:(BOOL)animated
{
[super viewDidDisappear:animated];
if (self.isPopInterActive)
{
self.navigationController.delegate = nil;
}
} #pragma mark - UINavigationController
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
if (self.isPopInterActive)
{
return [[ZJSliderTransitionDelegateObj alloc] init];
} else
{
return nil;
}
} - (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController
{
if (self.isPopInterActive)
{
return self.sliderTransition;
}
return nil; }
然后,在masterViewController部分,push一个新的ZJViewController即可。具体的效果请自行编译运行文后的源码。
从构建一个交互式的transition可以看到,交互式本身就被设计为一个单独的“模块”,方便开发的时候集成。这也再次体现出苹果对开发者的“体贴”。
最后附上本篇的代码下载地址。
由于最近转战C,iOS的内容拖了又拖,如果有疏漏的地方,欢迎大家指正,谢谢!
iOS之Custom UIViewController Transition的更多相关文章
- IOS 7 Study - UIViewController
Presenting and Managing Views with UIViewController ProblemYou want to switch among different views ...
- iOS 中的 Delayed Transition
Android 的动画体系中,存在一类由 TransitionManager. beginDelayedTransition 管理的动画.这个方法,很特殊.执行此方法后,其后续的 UI 变化,不会立即 ...
- 【iOS开发】UIViewController的基本概念与生命周期
http://www.cnblogs.com/wayne23/p/3868535.html UIViewController是iOS顶层视图的载体及控制器,用户与程序界面的交互都是由UIViewCon ...
- iOS模拟器Custom Location被重置解决方案
转自王中周的技术博客 问题说明 在做地图类应用时,经常需要用到位置模拟功能.iOS模拟器提供了该功能,我们可以设置指定的经纬度,选中模拟器后,按照以下菜单层次进入即可设置: Debug --> ...
- ios控件 UIViewController
//通过xib文件创建一个视图控制器.并作为窗口的根控制器 self.viewController = [[ViewController alloc] initWithNibName:@"V ...
- 学习笔记:iOS 视图控制器(UIViewController)剖析
转自:http://www.cnblogs.com/martin1009/archive/2012/06/01/2531136.html 视图控制器在iOS编程中占据非常重要的位置,因此我们一定要掌握 ...
- IOS 判断当前UIViewController 是否正在显示
我通常的做法是根据视图控制器的生命周期来判断,其是否是正在使用的状态. 举例 设一个实例布尔变量isVisible 在 -ViewWillAppear 里面 isVisible = YES ; 在 ...
- iOS - UITableViewCell Custom Selection Style Color
Customize UITextView selection color in UITableView Link : http://derekneely.com/2010/01/uitableview ...
- iOS弹出UIViewController小视图
在TestViewController1中弹出TestViewController2 在TestViewController中点击按钮或者什么触发方法里面写入以下代码 TestViewControll ...
随机推荐
- C# 五大修饰符
修饰符 访问权限 public 关键字是类型和类型成员的访问修饰符. 公共访问是允许的最高访问级别. 对访问公共成员没有限制 private 私有访问是允许的最低访问级别. 私有成员只有在声明它们的类 ...
- Linux入门-9 软件管理基础(CentOS)
0. 源代码形式 1. RPM软件包管理 RPM RPM查询 RPM验证 2. YUM软件管理 YUM基本命令 YUM查询 创建YUM仓库 0. 源代码形式 绝大多数开源软件都是直接以源代码形式发布 ...
- nginx+php+swoole安装记录
领了台阿里服务器1vCPU 1G,做下测试研究. 系统 centos7,使用yum安装. Nginx yum install nginx ##开启nginx service nginx start 安 ...
- 组合数计算-java
排列组合是计算应用经常使用的算法,通常使用递归的方式计算,但是由于n!的过于大,暴力计算很不明智.一般使用以下两种方式计算. 一,递归的思想:假设m中取n个数计算排列组合数,表示为comb(m,n). ...
- 初始python(二)
1. 列表list 1.1 切片# 定义一个list.list = [1, 2, 3, 4, 5] 从左往右读取字符(默认步长为 1 ).如:list[-2:-1] # 返回一个list数据类型,[ ...
- HTTP协议图--HTTP 协议报文结构
1.HTTP 报文 用于 HTTP 协议交互的信息被称为 HTTP 报文.请求端(客户端)的 HTTP 报文叫做请求报文:响应端(服务器端)的叫做响应报文.HTTP 报文本身是由多行(用 CR[car ...
- vue怎么不通过dom操作获取dom节点
今天写一个公众号的项目,写了一个vue的搜索组件,点击搜索框时,背景出现一个遮罩,代码结构如下: template:`<div class="searchBar-div"&g ...
- 015.1 Lock接口
内容:Lock接口使用步骤,同步生产大白兔奶糖的例子 同步代码块的锁是隐式的,显式容易让我们理解.所以我们使用这个显式的方法,方便理解代码.######实现同步步骤:1.获取锁:lock()2.同步代 ...
- chrome浏览器Network面板请求Timing分析
Timing显示资源在整个请求生命周期过程中各部分话费的时间. Queueing 排队的时间花费.可能由于该请求被渲染引擎认为是优先级比较低的资源(图片).服务器不可用.超过浏览器并发请求的最大连接数 ...
- POJ-3662 Telephone Lines---二分+最短路+最小化第k+1大
题目链接: https://cn.vjudge.net/problem/POJ-3662 题目大意: 求一条路径从1到n使第k+1大的边最小. 解题思路: 二分答案mid,当原边权小于等于mid新边权 ...