像我們平常用的最多的就是presentViewController:animated:completion:和對(duì)應(yīng)的dismissViewControllerAnimated:completion:來實(shí)現(xiàn)展示視圖和隱藏視圖。最近看了官方的Demo,對(duì)于我這種小菜鳥著實(shí)下了一番功夫才搞明白。詳細(xì)代碼可以在這里下載,如果覺得有用,可以隨手star一下。
動(dòng)畫效果 只實(shí)現(xiàn)了其中的一部分

第一個(gè)效果(Cross Dissolve)
. 1 首先當(dāng)前的控制器需要遵守UIViewControllerTransitioningDelegate點(diǎn)擊command進(jìn)去可以看到有5個(gè)代理方法,由于當(dāng)前的動(dòng)畫沒有涉及到用戶的交互情況,所以只實(shí)現(xiàn)了前兩個(gè)方法,它返回的是一個(gè)遵守了UIViewControllerAnimatedTransitioning的id對(duì)象,UIViewControllerAnimatedTransitioning它是用來控制動(dòng)畫的持續(xù)時(shí)間和展示邏輯。
#import <UIKit/UIKit.h>
@interface CrossDissolveAnimator : NSObject<UIViewControllerAnimatedTransitioning>
@end
#import "CrossDissolveAnimator.h"
@implementation CrossDissolveAnimator
//動(dòng)畫持續(xù)的時(shí)長
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext{
return 0.35;
}
//動(dòng)畫相關(guān)的參數(shù)
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
//動(dòng)畫相關(guān)聯(lián)的兩個(gè)控制器
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//containerView:本人理解是如果沒有動(dòng)畫,則為fromView,如果在動(dòng)畫的過程中,則為toView
UIView *containerView = transitionContext.containerView;
UIView *fromView;
UIView *toView;
//iOS8引入了viewForKey方法,系統(tǒng)會(huì)優(yōu)先訪問這個(gè)方法,另外viewForKey這個(gè)方法有可能會(huì)返回nil,盡量不直接訪問controller的view屬性,就比如第三個(gè)demo從底部彈出的動(dòng)畫,給presentedViewController的view添加了圓角陰影等效果,viewForKey訪問到的是所有的view 而presentedViewController的view只能訪問到其中的一個(gè)子視圖
if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
toView = [transitionContext viewForKey:UITransitionContextToViewKey];
}else{
fromView = fromViewController.view;
toView = toViewController.view;
}
fromView.frame = [transitionContext initialFrameForViewController:fromViewController];
toView.frame = [transitionContext finalFrameForViewController:toViewController];
fromView.alpha = 1.0f;
toView.alpha = 0.0f;
//在present和,dismiss時(shí),必須將toview添加到視圖層次中
[containerView addSubview:toView];
NSTimeInterval transitionDurating = [self transitionDuration:transitionContext];
[UIView animateWithDuration:transitionDurating animations:^{
fromView.alpha = 0.0f;
toView.alpha = 1.0f;
} completion:^(BOOL finished) {
BOOL wasCancelled = [transitionContext transitionWasCancelled];
[transitionContext completeTransition:!wasCancelled];
}];
}
@end
以上為核心的代碼,這里借用下別人的圖片來注明一下用到的fromView和toView

另外需要注意,
modalPresentationStyle需要是UIModalPresentationFullScreen,full跟Custom的本質(zhì)區(qū)別是full會(huì)移除fromView,但是Custom卻不會(huì)。
第二個(gè)效果(Swipe)
首先先分析,它既可以點(diǎn)擊自定義跳轉(zhuǎn),其次又可以添加手勢來滑動(dòng),所以不僅要實(shí)現(xiàn)UIViewControllerTransitioningDelegate中的方法而且我們還要計(jì)算在手勢拖動(dòng)中百分比的控制,官方已經(jīng)封裝好了我們只需要實(shí)現(xiàn)UIPercentDrivenInteractiveTransition協(xié)議就好了。并且當(dāng)前的轉(zhuǎn)場動(dòng)畫是交互式的動(dòng)畫,判斷的依據(jù)就是可以手勢滑動(dòng)。
.1 自定義一個(gè)類遵守UIViewControllerTransitioningDelegate通過動(dòng)畫我們可以看出,當(dāng)present的時(shí)候是從屏幕的右邊開始,dismiss從左邊開始消失,所以我們要計(jì)算兩個(gè)toView的frame值
首先實(shí)現(xiàn)點(diǎn)擊Button來轉(zhuǎn)場
#import <UIKit/UIKit.h>
@interface SwipeTransitionAnimatar : NSObject<UIViewControllerAnimatedTransitioning>
@property(nonatomic,assign) UIRectEdge edge;//用來判斷是左滑還是右滑
- (instancetype)initWithTargetEdge:(UIRectEdge)dege;
@end
#import "SwipeTransitionAnimatar.h"
@implementation SwipeTransitionAnimatar
- (instancetype)initWithTargetEdge:(UIRectEdge)dege{
if (self = [super init]) {
_edge = dege;
}
return self;
}
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
{
return 0.35f;
}
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = transitionContext.containerView;
UIView *fromView;
UIView *toView;
if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
toView = [transitionContext viewForKey:UITransitionContextToViewKey];
}else{
fromView = fromViewController.view;
toView = toViewController.view;
}
// isPresenting用于判斷當(dāng)前是present還是dismiss
BOOL isPresenting = (toViewController.presentingViewController == fromViewController);
CGRect fromFrame = [transitionContext initialFrameForViewController:fromViewController];
CGRect toFrame = [transitionContext finalFrameForViewController:toViewController];
__block CGVector offset;//用于計(jì)算toView的位置
//定義一個(gè)二維矢量運(yùn)動(dòng)的方向,重力方向默認(rèn)的是(0.f,1.f),dx為-1.0f時(shí)向左運(yùn)動(dòng),dy為-1.0時(shí)向上運(yùn)動(dòng)
if (self.edge == UIRectEdgeLeft){
offset = CGVectorMake(1.f, 0.f);//從左邊屏幕開始滑動(dòng)表示dismiss時(shí)
}else if (self.edge == UIRectEdgeRight){
offset = CGVectorMake(-1.f, 0.f);//從右邊開始滑動(dòng)表示present時(shí)
}else{
NSAssert(NO, @"targetEdge must be one of UIRectEdgeLeft, or UIRectEdgeRight.");
}
//根據(jù)當(dāng)前是present還是dismiss來計(jì)算好toView的初始位置以及結(jié)束位置
if (isPresenting) {
fromView.frame = fromFrame;
toView.frame = CGRectOffset(toFrame, toFrame.size.width*offset.dx*-1, toFrame.size.height*offset.dy*-1);
}else{
fromView.frame = fromFrame;
toView.frame = toFrame;
}
if (isPresenting) {
[containerView addSubview:toView];
}else{
[containerView insertSubview:toView belowSubview:fromView];
}
NSTimeInterval transitionDurating = [self transitionDuration:transitionContext];
[UIView animateWithDuration:transitionDurating animations:^{
if (isPresenting) {
toView.frame = toFrame;
}else{
fromView.frame = CGRectOffset(toFrame, toFrame.size.width*offset.dx, toFrame.size.height*offset.dy);
}
} completion:^(BOOL finished) {
BOOL wasCancelled = [transitionContext transitionWasCancelled];
if (wasCancelled)
[toView removeFromSuperview];
[transitionContext completeTransition:!wasCancelled];
}];
}
@end
.2 自定義動(dòng)畫百分比控制器來實(shí)現(xiàn)手勢滑動(dòng)的時(shí)候所占的百分比
#import "SwipePercentInteractionController.h"
@interface SwipePercentInteractionController ()
@property(nonatomic,strong)id<UIViewControllerContextTransitioning> transitionContext;//用來存儲(chǔ)實(shí)時(shí)的transitionContext
@property(nonatomic,strong)UIScreenEdgePanGestureRecognizer *pan;
@property(nonatomic,assign) UIRectEdge edge;
@end
@implementation SwipePercentInteractionController
- (instancetype)initWithGestureRecognizer:(UIScreenEdgePanGestureRecognizer *)pan edgeForDragging:(UIRectEdge)edge{
if (self = [super init]) {
_pan = pan;
_edge = edge;
[_pan addTarget:self action:@selector(pan:)];
}
return self;
}
- (instancetype)init{
@throw [NSException exceptionWithName:NSInvalidArgumentException reason:@"Use -initWithGestureRecognizer:edgeForDragging:" userInfo:nil];
}
- (void)dealloc{
[self.pan removeTarget:self action:@selector(pan:)];
}
//不應(yīng)該緩存transitionContext,而是動(dòng)態(tài)的獲取,這樣保證拿到的始終是最新的,最正確的消息
-(void)startInteractiveTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
self.transitionContext = transitionContext;
[super startInteractiveTransition:transitionContext];
}
//根據(jù)當(dāng)前手勢觸摸的點(diǎn)從而計(jì)算出偏移的百分比
- (CGFloat)percentForGuesture:(UIScreenEdgePanGestureRecognizer *)pan{
UIView *transitionContainerView = self.transitionContext.containerView;
CGPoint location = [pan locationInView:transitionContainerView];
CGFloat width = CGRectGetWidth(transitionContainerView.bounds);
if (self.edge == UIRectEdgeLeft) {
return location.x/width;
}else if (self.edge == UIRectEdgeRight){
return (width - location.x)/width;
}else
return 0.f;
}
//手勢滑動(dòng)觸發(fā)方法
- (void)pan:(UIScreenEdgePanGestureRecognizer *)pan{
switch (pan.state) {
case UIGestureRecognizerStateBegan:
break;
case UIGestureRecognizerStateChanged://手勢滑動(dòng),更新百分比
[self updateInteractiveTransition:[self percentForGuesture:pan]];
break;
case UIGestureRecognizerStateEnded: // 滑動(dòng)結(jié)束,判斷是否超過一半,如果是則完成剩下的動(dòng)畫,否則取消動(dòng)畫
if ([self percentForGuesture:pan] >= 0.5f) {
[self finishInteractiveTransition];
}else{
[self cancelInteractiveTransition];
}
break;
default:
[self cancelInteractiveTransition];
break;
}
}
@end
.3 實(shí)現(xiàn)UIViewControllerTransitioningDelegate代理方法
@implementation SwipeTransitioningDelegate
// === 點(diǎn)擊Button的時(shí)候走下面這兩個(gè)方法 ===
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
return [[SwipeTransitionAnimatar alloc]initWithTargetEdge:self.edge];
}
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
return [[SwipeTransitionAnimatar alloc]initWithTargetEdge:self.edge];
}
//=== 用手勢滑動(dòng)的時(shí)候走這兩個(gè)方法 ====
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator{
if (self.pan) {
return [[SwipePercentInteractionController alloc]initWithGestureRecognizer:self.pan edgeForDragging:self.edge];
}
else
return nil;
}
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator{
if (self.pan) {
return [[SwipePercentInteractionController alloc]initWithGestureRecognizer:self.pan edgeForDragging:self.edge];
}
else
return nil;
}
@end
第三個(gè)效果(Custom Presentation)
分析動(dòng)畫我們可以知道,presented出來的view自定義的有陰影,有圓角等而且frame并不是整個(gè)屏幕,這種比較徹底的自定義的presentedView我們可以使用轉(zhuǎn)場協(xié)調(diào)器UIPresentationController來實(shí)現(xiàn),它在轉(zhuǎn)場動(dòng)畫的執(zhí)行過程中一直存在。UIPresentationController具有以下的功能:
1.設(shè)置presentedViewController的視圖大小
2.添加自定義視圖來改變presentedView的外觀
3.為任何自定義的視圖提供轉(zhuǎn)場動(dòng)畫效果
4.根據(jù)size class進(jìn)行響應(yīng)式布局
由于```UIPresentationController``是一個(gè)基類,所以我們需要自定義實(shí)現(xiàn)一個(gè)類來集成它。該類主要的四個(gè)方法,顧名思義,就是在轉(zhuǎn)場動(dòng)畫將要開始(在這里定義視圖層級(jí)結(jié)構(gòu)和frame),開始,將要結(jié)束,結(jié)束時(shí)的操作。
- (void)presentationTransitionWillBegin;
- (void)presentationTransitionDidEnd:(BOOL)completed;
- (void)dismissalTransitionWillBegin;
- (void)dismissalTransitionDidEnd:(BOOL)completed;
#import "CustomPresentatitionTransitioning.h"
@interface CustomPresentatitionTransitioning ()<UIViewControllerAnimatedTransitioning>
@property(nonatomic,strong)UIView *dimmingView;//后面的視圖遮罩
@property(nonatomic,strong)UIView *presentationWrappingView;//添加動(dòng)畫效果的view
@end
@implementation CustomPresentatitionTransitioning
// ============= UIPresentationController初始化 方法 ==================
- (instancetype)initWithPresentedViewController:(UIViewController *)presentedViewController presentingViewController:(UIViewController *)presentingViewController{
if (self = [super initWithPresentedViewController:presentedViewController presentingViewController:presentingViewController]) {
//Custom不會(huì)移除presentingView
presentedViewController.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
- (UIView *)presentedView{
return self.presentationWrappingView;
}
//轉(zhuǎn)場將要開始的時(shí)候,布局視圖的層級(jí)結(jié)構(gòu)和frame
- (void)presentationTransitionWillBegin{
//可以更改自身的視圖層級(jí),添加額外的效果(陰影,圓角)
UIView *presentedViewControllerView = [super presentedView];
presentedViewControllerView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
/*運(yùn)行的效果是由三個(gè)View疊加在一起形成的效果層級(jí)關(guān)系是
presentationWapperView(負(fù)責(zé)陰影)
presentationRoundedCornerView(負(fù)責(zé)圓角)
presentedViewControllerWrapperView
presentedViewControllerView
*/
UIView *presentationWapperView = [[UIView alloc]initWithFrame:self.frameOfPresentedViewInContainerView];
presentationWapperView.layer.shadowOpacity = 0.44f;//陰影不透明
presentationWapperView.layer.shadowRadius = 13.f;
presentationWapperView.layer.shadowOffset = CGSizeMake(0, -6.f);
self.presentationWrappingView = presentationWapperView;
UIView *presentationRoundedCornerView = [[UIView alloc]initWithFrame:UIEdgeInsetsInsetRect(presentationWapperView.bounds, UIEdgeInsetsMake(0, 0, -16, 0))];
presentationRoundedCornerView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
presentationRoundedCornerView.layer.cornerRadius = 16;
presentationRoundedCornerView.layer.masksToBounds = YES;
UIView *presentedViewControllerWrapperView = [[UIView alloc]initWithFrame:UIEdgeInsetsInsetRect(presentationRoundedCornerView.bounds, UIEdgeInsetsMake(0, 0, 16, 0))];
presentedViewControllerWrapperView.autoresizingMask = UIViewAutoresizingFlexibleWidth |UIViewAutoresizingFlexibleHeight;
presentedViewControllerView.frame = presentedViewControllerWrapperView.bounds;
//層級(jí)關(guān)系
[presentationWapperView addSubview:presentationRoundedCornerView];
[presentationRoundedCornerView addSubview:presentedViewControllerWrapperView];
[presentedViewControllerWrapperView addSubview:presentedViewControllerView];
//添加一個(gè)dimmingview(昏暗)在presentationWrapperView的后面,然后再添加self.presentedView 這樣的話dimmingview都出現(xiàn)在presentedView的后面
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(tap:)]];
self.dimmingView = dimmingView;
[self.containerView addSubview:dimmingView];
id <UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
self.dimmingView.alpha = 0.f;
[transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
self.dimmingView.alpha = 0.5;
} completion:nil];
}
//轉(zhuǎn)場完成時(shí)清除多余的視圖
- (void)presentationTransitionDidEnd:(BOOL)completed{
//如果present沒有完成,把dimmingView和wrappingView都清空,這些臨時(shí)視圖用不到了
if (completed == NO) {
self.presentationWrappingView = nil;
self.dimmingView = nil;
}
}
- (void)dismissalTransitionWillBegin{
id <UIViewControllerTransitionCoordinator> transitionCoordinator = self.presentingViewController.transitionCoordinator;
[transitionCoordinator animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> _Nonnull context) {
self.dimmingView.alpha = 0.f;
} completion:nil];
}
- (void)dismissalTransitionDidEnd:(BOOL)completed{
if (completed) {
self.presentationWrappingView = nil;
self.dimmingView = nil;
}
}
// ============= 如果當(dāng)前DidChange的視圖是presentedViewController,UIContentContainer重新布局子視圖 ==========
- (void)preferredContentSizeDidChangeForChildContentContainer:(id<UIContentContainer>)container{
[super preferredContentSizeDidChangeForChildContentContainer:container];
if (container == self.presentedViewController) {
[self.containerView setNeedsLayout];
}
}
- (CGSize)sizeForChildContentContainer:(id<UIContentContainer>)container withParentContainerSize:(CGSize)parentSize{
if (container == self.presentedViewController) {
return ((UIViewController *)container).preferredContentSize;
}else
return [super sizeForChildContentContainer:container withParentContainerSize:parentSize];
}
//重寫系統(tǒng)方法計(jì)算presentedViewController的大小
- (CGRect)frameOfPresentedViewInContainerView{
CGRect containerViewBounds = self.containerView.bounds;
CGSize presentedViewContentSize = [self sizeForChildContentContainer:self.presentedViewController withParentContainerSize:containerViewBounds.size];
CGRect presentedViewControllerFrame = CGRectMake(containerViewBounds.origin.x, CGRectGetMaxY(containerViewBounds) - presentedViewContentSize.height, presentedViewContentSize.width, presentedViewContentSize.height);
return presentedViewControllerFrame;
}
- (void)containerViewWillLayoutSubviews{
[super containerViewWillLayoutSubviews];
self.dimmingView.frame = self.containerView.bounds;
self.presentationWrappingView.frame = self.frameOfPresentedViewInContainerView;
}
- (IBAction)tap:(UITapGestureRecognizer *)tap{
[self.presentingViewController dismissViewControllerAnimated:YES completion:nil];
}
@end
總結(jié):
其實(shí)實(shí)現(xiàn)自定義轉(zhuǎn)場動(dòng)畫無非就是以下幾步:
1.設(shè)置將要跳轉(zhuǎn)的目的控制器presentedViewController的transitationingDelegate
2.充當(dāng)代理的對(duì)象可以是 self(比較簡單的動(dòng)畫)也可以是自定義的一個(gè)實(shí)現(xiàn)了UIViewControllerTransitioningDelegate的類。
3.在2的該類中實(shí)現(xiàn)指定的代理方法返回對(duì)應(yīng)的Animater,Animater是實(shí)現(xiàn)了UIViewControllerAnimatedTransitioning的對(duì)象,轉(zhuǎn)場動(dòng)畫的所有的核心代碼都是在該Animater中實(shí)現(xiàn)
自定義動(dòng)畫還有很多需要學(xué)習(xí)研究的地方,遠(yuǎn)遠(yuǎn)不止這些,以上僅為自己總結(jié)的一點(diǎn)點(diǎn)心得。還有幾個(gè)轉(zhuǎn)場動(dòng)畫有時(shí)間繼續(xù)更新!