iOS自定义转场动画/UIPresentationControl
2017-03-09 本文已影响1906人
苦笑男神
一.UIPresentationController简介
-
UIPresentationController
是 iOS8 新增的一个API,苹果的官方定义是:对象为所呈现的视图控制器提供高级视图的转换管理(从呈现视图控制器的时间直到它被消除期间)。其实说白了就是用来控制controller
之间的跳转特效。比如希望实现一个特效,显示一个窗口,大小和位置都是自定义的,并且遮罩在原来的页面上。 - 下图是苹果官方自定义modal动画的栗子,点我直接下载。
- 网上找到的
UIPresentationController
在自定义modal动画中的位置:
UIPresentationController作用.png
二.UIPresentationController作用
- 管理所有
Modal
出来的控制器 - 管理\监听 切换控制器的过程
- 控制器一旦调了
presentViewController
方法,控制器的presentationController,
会先创建好了,然后整个控制器的切换由presentationController
管理。如下代码:
AViewController *vc = [[AViewController alloc] init];
[self presentViewController:vc animated:YES completion:nil];
三.UIPresentationController方法和属性介绍
/**
构造方法,苹果建议使用这个初始化UIPresentationController
@param presentedViewController 将要跳转到的目标控制器
@param presentingViewController 跳转前的原控制器
*/
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController;
presentedViewController: 要 modal 显示的视图控制器
presentingViewController: 跳转前视图控制器
containerView() 容器视图
presentedView() 被展现控制器的视图
func presentationTransitionWillBegin() 跳转将要开始
func presentationTransitionDidEnd(completed: Bool) 跳转完成
func dismissalTransitionWillBegin() dismiss将要开始
func dismissalTransitionDidEnd(completed: Bool) dismiss完成
func frameOfPresentedViewInContainerView() 动画之后,目标控制器View的位置
四.自定义modal转场动画的第三个步骤
-
第一步.写一个遵守
UIViewControllerTransitioningDelegate
协议的类,来告诉控制器,谁是动画主管(UIPresentationController
),谁是开始动画的具体细节负责类、谁是结束动画的具体细节负责类。 -
第二步.写一个
UIPresentationController
的子类(动画主管) --> 负责「被呈现」及「负责呈现」的controller
以外的controller
,比如带渐变效果的黑色半透明背景View
。在此步骤,起码需要重写以下5个方法:1.presentationTransitionWillBegin
2.presentationTransitionDidEnd:
3.dismissalTransitionWillBegin
4.dismissalTransitionDidEnd:
5.frameOfPresentedViewInContainerView
-
第三步.写一个遵守
UIViewControllerAnimatedTransitioning
协议的类,负责动画细节。比如怎么出现,位置在哪,动画细节如何等。 -
下面将通过几个Demo,来实现自定义转场动画,建议直接下载Demo运行看效果,gif图有点失真失帧。
-
Demo下载地址: 点我下载
Demo效果图.gif
五.自定义modal转场动画第一个Demo
- 第一个Demo比较简单,不用我们自己去写动画效果,这里将贴出部分代码,后续Demo将不贴代码了,可以自己去下载Demo看看,里面加了很详细的注释。
- 按照上面所写的步骤,为了方便,我们将第一步、第二步合并在一起,使用一个类同时实现
UIViewControllerTransitioningDelegate
协议,并继承UIPresentationController
- h文件的内容:
#import <UIKit/UIKit.h>
/**
* 实现自定义过渡动画:
* 1.继承UIPresentationController 成为子类
* 2.遵守UIViewControllerAnimatedTransitioning 协议
* 其实也可以写成两个类,分别继承UIPresentationController和实现UIViewControllerAnimatedTransitioning协议
*/
@interface BCustomPresentationController : UIPresentationController <UIViewControllerTransitioningDelegate>
/** 黑色半透明背景 */
@property (nonatomic, strong) UIView *dimmingView;
@end
- m文件的内容:
@implementation BCustomPresentationController
//| ------------------------------第一步内容----------------------------------------------
#pragma mark - UIViewControllerTransitioningDelegate
/*
* 来告诉控制器,谁是动画主管(UIPresentationController),因为此类继承了UIPresentationController,就返回了self
*/
- (UIPresentationController* )presentationControllerForPresentedViewController:(UIViewController *)presented presentingViewController:(UIViewController *)presenting sourceViewController:(UIViewController *)source {
return self;
}
//| ------------------------------第二步内容----------------------------------------------
#pragma mark - 重写UIPresentationController个别方法
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController {
self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController];
if (self) {
// 必须设置 presentedViewController 的 modalPresentationStyle
// 在自定义动画效果的情况下,苹果强烈建议设置为 UIModalPresentationCustom
presentedViewController.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
// 呈现过渡即将开始的时候被调用的
// 可以在此方法创建和设置自定义动画所需的view
- (void)presentationTransitionWillBegin {
// 背景遮罩
UIView *dimmingView = [[UIView alloc] initWithFrame:self.containerView.bounds];
dimmingView.backgroundColor = [UIColor blackColor];
dimmingView.opaque = NO; //是否透明
dimmingView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
[dimmingView addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(dimmingViewTapped:)]];
self.dimmingView = dimmingView;
[self.containerView addSubview:dimmingView]; // 添加到动画容器View中。
// 获取presentingViewController 的转换协调器,应该动画期间的一个类?上下文?之类的,负责动画的一个东西
id<UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
// 动画期间,背景View的动画方式
self.dimmingView.alpha = 0.f;
[transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.dimmingView.alpha = 0.4f;
} completion:NULL];
}
#pragma mark 点击了背景遮罩view
- (void)dimmingViewTapped:(UITapGestureRecognizer*)sender {
[self.presentingViewController dismissViewControllerAnimated:YES completion:NULL];
}
// 在呈现过渡结束时被调用的,并且该方法提供一个布尔变量来判断过渡效果是否完成
- (void)presentationTransitionDidEnd:(BOOL)completed {
// 在取消动画的情况下,可能为NO,这种情况下,应该取消视图的引用,防止视图没有释放
if (!completed) {
self.dimmingView = nil;
}
}
// 消失过渡即将开始的时候被调用的
- (void)dismissalTransitionWillBegin {
id<UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
[transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
self.dimmingView.alpha = 0.f;
} completion:NULL];
}
// 消失过渡完成之后调用,此时应该将视图移除,防止强引用
- (void)dismissalTransitionDidEnd:(BOOL)completed {
if (completed == YES) {
[self.dimmingView removeFromSuperview];
self.dimmingView = nil;
}
}
// 返回目标控制器Viewframe
- (CGRect)frameOfPresentedViewInContainerView {
// 这里直接按照想要的大小写死,其实这样写不好,在第二个Demo里,我们将按照苹果官方Demo,写灵活的获取方式。
CGFloat height = 300.f;
CGRect containerViewBounds = self.containerView.bounds;
containerViewBounds.origin.y = containerViewBounds.size.height - height;
containerViewBounds.size.height = height;
return containerViewBounds;
}
// 建议就这样重写就行,这个应该是控制器内容大小变化时,就会调用这个方法, 比如适配横竖屏幕时,翻转屏幕时
// 可以使用UIContentContainer的方法来调整任何子视图控制器的大小或位置。
- (void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container {
[super preferredContentSizeDidChangeForChildContentContainer:container];
if (container == self.presentedViewController) [self.containerView setNeedsLayout];
}
- (void)containerViewWillLayoutSubviews {
[super containerViewWillLayoutSubviews];
self.dimmingView.frame = self.containerView.bounds;
}
@end
- 疑问:为什么Demo1里没有按照三个步骤里指定 动画的具体细节实现类,也有动画效果呢?
- 因为在 弹出控制器时,指定了
animated
为YES,那么此时如果没有实现UIViewControllerAnimatedTransitioning
动画细节类,此时就会使用系统的presentViewController
动画效果,即:从下往上弹出,从上往下消失。
六.自定义modal转场动画第二个Demo
- 此Demo2在Demo1的基础上,实现了
UIViewControllerAnimatedTransitioning
协议,实现了对动画效果的定制,UIViewControllerAnimatedTransitioning
协议主要实现以下几个方法:
/| ------------------------------第三步内容----------------------------------------------
#pragma mark UIViewControllerAnimatedTransitioning具体动画实现
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext {
// 动画时长
return 0.45 ;
}
// 核心,动画效果的实现
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext {
// 1.获取源控制器、目标控制器、动画容器View
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
__unused UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = transitionContext.containerView;
// 2. 获取源控制器、目标控制器 的View,但是注意二者在开始动画,消失动画,身份是不一样的:
// 也可以直接通过上面获取控制器获取,比如:toViewController.view
UIView *toView = [transitionContext viewForKey:UITransitionContextToViewKey];
UIView *fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
[containerView addSubview:toView]; //必须添加到动画容器View上。
// 3.设置动画具体细节,使用[UIView animate...]动画,或者其他方式呈现动画。
// 4.动画结束时,必须执行下句代码
[transitionContext completeTransition:YES];
}
- (void)animationEnded:(BOOL) transitionCompleted {
// 动画结束...
}
- 在Demo1里,我们在
UIPresentationController
里写死了目标控制器View的位置大小,从面向对象的角度讲,是不合理的,目标控制器View的大小,它自己最清楚,所在我们在Demo2里,仿照苹果官方Demo,完善了这一个问题。(其实如果在上面UIViewControllerAnimatedTransitioning
协议里实现了动画前后的位置和大小,那么在UIPresentationController
可以不再重写size
和frame
相关的几个方法)
七.自定义modal转场动画第三个Demo
- 此效果的实现思路,灵感是从控制器
modalPresentationStyle
属性值上来的,当modalPresentationStyle
为UIModalPresentationCustom
的时候,就presentViewController
目标控制器之后,把目标控制器的view设置为透明,就能看到源控制器。
toVC.modalPresentationStyle = UIModalPresentationCustom ; //必须是UIModalPresentationCustom
toVC.view.backgroundColor = [UIColor clearColor]; //必须是clearColor
[self presentViewController:toVC animated:NO completion:nil]; //animated:必须是NO
- 基于以上对目标控制的设置之后,就可以在目标控制器的
viewWillAppear:
方法里自定义动画效果。核心代码如下:(建议下载Demo查看详细注释)
#pragma mark - 在此方法做动画呈现
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.bgView.frame = [UIScreen mainScreen].bounds;
self.contentView.frame = CGRectMake(0,[UIScreen mainScreen].bounds.size.height, CGRectGetWidth([UIScreen mainScreen].bounds), 300.0);
[UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.95 initialSpringVelocity:0.05 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.bgView.alpha = 1.0f ;
self.contentView.frame = CGRectMake(0, [UIScreen mainScreen].bounds.size.height - 300.f, CGRectGetWidth([UIScreen mainScreen].bounds), 300.0f);
} completion:^(BOOL finished) {
}];
}
#pragma mark - 消失
- (void)clickBgView {
[UIView animateWithDuration:0.4 delay:0 usingSpringWithDamping:0.95 initialSpringVelocity:0.05 options:UIViewAnimationOptionCurveEaseInOut animations:^{
self.bgView.alpha = 0.0f ;
self.contentView.frame = CGRectMake(0,[UIScreen mainScreen].bounds.size.height, CGRectGetWidth([UIScreen mainScreen].bounds), 300.0);
} completion:^(BOOL finished) {
// 动画Animated必须是NO,不然消失之后,会有0.35s时间,再点击无效
[self dismissViewControllerAnimated:NO completion:nil];
}];
}
END。
我是小侯爷。
在魔都艰苦奋斗,白天是上班族,晚上是知识服务工作者。
如果读完觉得有收获的话,记得关注和点赞哦。
非要打赏的话,我也是不会拒绝的。