自定义转场详解(一)
前言
本文是我学习了onevcat的这篇转场入门做的一点笔记。
今天我们来实现一个简单的自定义转场,我们先来看看这篇文章将要实现的一个效果图吧:
过程详解
热身准备
我们先创建一个工程,首先用storyboard快速的创建两个控制器,一个作为主控制器,叫ViewController
,另外一个作为present出来的控制器,叫PresentViewController
,并且用autoLayout快速搭建好界面。就像这样:
我们先做好点击ViewController
上面的按钮,present出 PresentViewController
,点击PresentViewController
上面的按钮,dismiss掉PresentViewController
的逻辑。这里有两个注意点:
- 因为此处我使用了
segue
,所以在ViewController
按钮点击的时候,我们只需要这样调用就行。 #pragma mark - 点我弹出 -(IBAction)presentBtnClick:(UIButton *)sender { [self performSegueWithIdentifier:@"PresentSegue" sender:nil]; } - 我们平时写dismiss的时候,一般都会是在第二个控制器中直接给self发送
dismissViewController
的相关方法。在现在的SDK中,如果当前的VC是被显示的话,这个消息会被直接转发到显示它的VC去。但是这并不是一个好的实现,违反了程序设计的哲学,也很容易掉到坑里。所以我们用标准的delegate
方式实现dismiss
。
首先我们在PresentViewController
控制器中申明一个代理方法。
#import <UIKit/UIKit.h>
@class PresentViewController;
@protocol PresentViewControllerDelegate <NSObject>
- (void)dismissViewController:(PresentViewController *)viewController;
@end
@interface PresentViewController : UIViewController
@property (nonatomic, weak) id<PresentViewControllerDelegate> delegate;
@end
在button的点击事件中,让代理去完成关闭当前控制器的工作。
#pragma mark - 点击关闭
- (IBAction)closeBtnClick:(UIButton *)sender {
if (self.delegate && [self.delegate respondsToSelector:@selector(dismissViewController:)]) {
[self.delegate dismissViewController:self];
}
}
与此同时,在ViewController
中需要设置PresentViewController
的代理,并且实现代理方法:
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"PresentSegue"]) {
PresentViewController *presetVC = segue.destinationViewController;
presetVC.delegate = self;
}
}
#pragma mark - PresentViewControllerDelegate
- (void)dismissViewController:(PresentViewController *)viewController {
[self dismissViewControllerAnimated:YES completion:nil];
}
OK,到这里,我们一个基本的转场就完成了(这也是系统自带的一个效果)。like this:
主要内容
接下来,要接触我们今天要讲的主要内容了,我们用iOS7中一个新的类UIViewControllerTransitioning
来实现自定义转场。
UIViewControllerAnimatedTransitioning
首先我们需要一个实现了协议名为UIViewControllerAnimatedTransitioning
的对象。创建一个类叫做PresentAnimation
继承于NSObject
并且实现了UIViewControllerAnimatedTransitioning
协议。(注意:需要导入UIKit框架)
@interface PresentAnimation : NSObject<UIViewControllerAnimatedTransitioning>
这个协议负责转场的具体内容。开发者在做自定义切换效果时大部门代码会是用来实现这个协议的,这个协议只有两个方法必须要实现的:
// 返回动画的时间
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// 在进行切换的时候将调用该方法,我们对于切换时的UIView的设置和动画都在这个方法中完成。
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
实现这两个方法
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext {
return 0.8f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext {
// 1.我们需要得到参与切换的两个ViewController的信息,使用context的方法拿到它们的参照;
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
// 2.对于要呈现的VC,我们希望它从屏幕下方出现,因此将初始位置设置到屏幕下边缘;
CGRect finaRect = [transitionContext finalFrameForViewController:toVC];
toVC.view.frame = CGRectOffset(finaRect, 0, [UIScreen mainScreen].bounds.size.height);
// 3.将view添加到containerView中;
[[transitionContext containerView] addSubview:toVC.view];
// 4.开始动画。这里的动画时间长度和切换时间长度一致。usingSpringWithDamping的UIView动画API是iOS7新加入的,描述了一个模拟弹簧动作的动画曲线;
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.6 initialSpringVelocity:0.0 options:UIViewAnimationOptionCurveLinear animations:^{
toVC.view.frame = finaRect;
} completion:^(BOOL finished) {
// 5.在动画结束后我们必须向context报告VC切换完成,是否成功。系统在接收到这个消息后,将对VC状态进行维护。
[transitionContext completeTransition:YES];
}];
}
注意点
UITransitionContextToViewControllerKey
与UITransitionContextFromViewControllerKey
比如从A present 出B,此时A是FromViewController
,B是ToViewController
如果从B dismiss 到A,此时A是ToViewController
,B是FromViewController
UIViewControllerTransitioningDelegate
这个接口的作用比较单一,在需要VC切换的时候系统会向实现了这个接口的对象询问是否需要使用自定义转场效果。
所以,一个比较好的地方是直接在主控制器ViewController
中实现这个协议。
在ViewController
中完成如下代码:
@interface ViewController ()<PresentViewControllerDelegate,UIViewControllerTransitioningDelegate>
@property (nonatomic, strong) PresentAnimation *presentAnimation;
@end
@implementation ViewController
#pragma mark - 懒加载
- (PresentAnimation *)presentAnimation {
if (!_presentAnimation) {
_presentAnimation = [[PresentAnimation alloc] init];
}
return _presentAnimation;
}
#pragma mark - UIViewControllerTransitioningDelegate
- (id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source {
return self.presentAnimation;
}
- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
if ([segue.identifier isEqualToString:@"PresentSegue"]) {
PresentViewController *presetVC = segue.destinationViewController;
presetVC.delegate = self;
presetVC.transitioningDelegate = self;
}
}
现在看下我们的效果:
相对于上面系统自带的效果来说,我们在present出第二个控制器的时候,带有弹簧效果。
手势驱动百分比切换
现在我们增加一个功能,就是用手势滑动来dismiss,通俗的说,就是让present出来的那个控制器使用手势dismiss。
- 创建一个类,继承自
UIPercentDrivenInteractiveTransition
2.既然传入了这个需要手势dismiss的VC,我们就需要保存一下,方便当前类在其他地方使用,所以我们新建一个属性来保存这个传入的VC。
3.和创建PresentAnimation
一样,我们创建一个一个DismissAnimation
类
4.最后,我们在主控制器中添加一个手势驱动的对象,一个dismiss转场的对象,然后懒加载。
完善
此时,我们运行程序,会发现以上代码尽管可以手势驱动了,但是点击按钮dismiss的功能无法使用了。这是因为如果只是返回self.paninterTransition,那么点击按钮dismiss的动画就会失效;如果只是返回nil,那么手势滑动的效果将会失效。综上所述,我们就得分情况考虑。 接下来我们就来完善一下。
ok,到此为止,我们的一个自定义转场动画就算了完成了。
- 最终版 Reflector v1.0 (+简单的反流程混淆)
- 性能&分布式&NewLife.XCode对无限数据的支持
- ASP.NET MVC下的异步Action的定义和执行原理
- 包学会之浅入浅出Vue.js:结业篇
- 迈克尔•戴尔:人工智能杀手?技术反乌托邦?不存在的
- 你知道吗?多个类多线程环境下静态构造函数的执行顺序
- 云端架构师养成之三:微信也在用的消息队列服务
- 现在 tensorflow和mxnet 很火,是否还有必要学习 scikit-learn 等框架?
- ASP.NET MVC基于标注特性的Model验证:将ValidationAttribute应用到参数上
- 改进版CodeTimer及XCode性能测试
- 常见测试术语解析
- 秦俊:开放 DevOps 敏捷开发套件,助力开发者驰骋云端
- 开源组件NanUI一周年-使用HTML/CSS/JS来构建.Net Winform应用程序界面
- 邱寒:新零售笔记(四)基于区块链大数据的人工智能
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- DC-7靶机渗透实战
- 判断是否"存在",还在用count?试试这条SQL语句,性能杠杠的!
- protoBuf-python学习笔记
- 编写高质量可维护的代码之优化逻辑判断
- Owl项目
- 非Spring项目如何注入Mapper
- Raspberry Pi开发实战
- Vulnhub之DC-2过关记录
- 关于链表,你该了解这些!
- R语言可视化操作数据挖掘
- 因为一个函数strtok踩坑,我被老工程师无情嘲笑了(一)
- 【日志架构】ELK Stack + Kafka 端到端练习
- 极客算法训练笔记(三),链表详细图解,别再逃避了朋友
- 我就感觉到快 —— zsh 和 oh my zsh 冷启动速度优化
- equals和HashCode深入理解(转)