iOS自定義轉(zhuǎn)場動畫

通過這幾天的學(xué)習(xí),我嘗試實(shí)現(xiàn)了四個效果,廢話不多說,先上效果圖:

DEMO ONE:一個神奇移動效果push動畫,支持手勢pop

神奇效果.gif

DEMO TWO:一個彈性的present動畫,支持手勢present和dismiss

彈性present.gif

DEMO THREE:一個翻頁push效果,支持手勢PUSH和POP

翻頁效果.gif

DEMO FOUR:一個小圓點(diǎn)擴(kuò)散present效果,支持手勢dimiss

小圓點(diǎn)擴(kuò)散.gif

準(zhǔn)備

從iOS7開始,蘋果提供了自定義轉(zhuǎn)場的API,模態(tài)推送present和dismiss,導(dǎo)航控制器的push和pop,標(biāo)簽控制器的控制器切換都可以自定義轉(zhuǎn)場了.
1.我們需要自定義一個遵循的<UIViewControllerAnimatedTransitioning>協(xié)議的動畫過渡管理對象,并實(shí)現(xiàn)兩個必須實(shí)現(xiàn)的方法:

 // 返回動畫事件
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext; 
// 所有的過渡動畫事務(wù)都在這個方法里面完成 
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

2.我們還需要自定義一個繼承于UIPercentDrivenInteractiveTransition的手勢過渡管理對象,我把它成為百分比手勢過渡管理對象,因?yàn)閯赢嫷倪^程是通過百分比控制的
3.成為相應(yīng)的代理,實(shí)現(xiàn)相應(yīng)的代理方法,返回我們前兩步自定義的對象就OK了 !
模態(tài)推送需要實(shí)現(xiàn)如下4個代理方法,iOS8新的那個方法我暫時還沒有發(fā)現(xiàn)它的用處,所以暫不討論.

//返回一個管理present動畫過渡的對象
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source;
//返回一個管理dismiss動畫過渡的對象
- (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed;
//返回一個管理present手勢過渡的對象
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator;
//返回一個管理dismiss動畫過渡的對象
- (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id <UIViewControllerAnimatedTransitioning>)animator;

導(dǎo)航控制器實(shí)現(xiàn)如下2個代理方法

//返回轉(zhuǎn)場動畫過渡管理對象
- (nullable id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController
                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>) animationController NS_AVAILABLE_IOS(7_0);
//返回手勢過渡管理對象
- (nullable id <UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController
                                   animationControllerForOperation:(UINavigationControllerOperation)operation
                                                fromViewController:(UIViewController *)fromVC
                                                  toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);

標(biāo)簽控制器也有相應(yīng)的兩個方法

//返回轉(zhuǎn)場動畫過渡管理對象
 - (nullable id <UIViewControllerInteractiveTransitioning>)tabBarController:(UITabBarController *)tabBarController
               interactionControllerForAnimationController: (id <UIViewControllerAnimatedTransitioning>)animationController NS_AVAILABLE_IOS(7_0);
 //返回手勢過渡管理對象
 - (nullable id <UIViewControllerAnimatedTransitioning>)tabBarController:(UITabBarController *)tabBarController
     animationControllerForTransitionFromViewController:(UIViewController *)fromVC
                                       toViewController:(UIViewController *)toVC  NS_AVAILABLE_IOS(7_0);

4.如果看著這些常常的代理方法名頭疼的話,沒關(guān)系,先在demo中用起來吧,慢慢就習(xí)慣了,其實(shí)哪種自定義轉(zhuǎn)場都只需要這3個步驟,如果不需要手勢控制,步驟2還可以取消,現(xiàn)在就讓我們動手來實(shí)現(xiàn)效果吧

Let's go!

DEMO ONE:神奇移動效果

1.我們首先創(chuàng)建2個控制器,為了方便我稱做present操作的為vc1、被present的為vc2,點(diǎn)擊一個控制器上的按鈕可以push出另一個控制器.
2.創(chuàng)建一個手勢過渡管理的類,我這里是PEInteractiveTransition,因?yàn)闊o論哪一種轉(zhuǎn)場,手勢控制的實(shí)質(zhì)都是一樣的,我干脆就把這個手勢過渡管理的類封裝了一下,具體可以在.h文件里面查看,在接下來的三個轉(zhuǎn)場效果中我們都可以便捷的是使用它:
PEInteractiveTransition.h

typedef void(^GestureConfig)();

//手勢轉(zhuǎn)場類型
typedef NS_ENUM(NSUInteger,PEInteractiveTransitionType){
    PEInteractiveTransitionTypePresent = 0,
    PEInteractiveTransitionTypeDismiss,
    PEInteractiveTransitionTypePush,
    PEInteractiveTransitionTypePop
};

//手勢方向
typedef NS_ENUM(NSUInteger,PEInteractiveTransitionGestureDirection){
    PEInteractiveTransitionGestureDirectionLeft = 0,
    PEInteractiveTransitionGestureDirectionRight,
    PEInteractiveTransitionGestureDirectionUp,
    PEInteractiveTransitionGestureDirectionDown
};

@interface PEInteractiveTransition : UIPercentDrivenInteractiveTransition
/** 記錄是否開始手勢,判斷pop操作是手勢出發(fā)還是返回鍵出發(fā) */
@property (nonatomic, assign)BOOL interation;
/** 觸發(fā)手勢present的時候config,在config中初始化并present需要彈出的控制器 */
@property (nonatomic, copy)GestureConfig presentConfig;
/** 觸發(fā)手勢push的時候config,在config中初始化并push需要彈出的控制器 */
@property (nonatomic, copy)GestureConfig pushConfig;

#pragma mark - ---初始化方法---
+ (instancetype)interactiveTransitionWithTransitionType:(PEInteractiveTransitionType)type GestureDirection:(PEInteractiveTransitionGestureDirection)direction;
- (instancetype)initWithTransitionType:(PEInteractiveTransitionType)type GestureDirection:(PEInteractiveTransitionGestureDirection)direction;

/** 給傳入控制器添加手勢*/
- (void)addPanGestureForViewController:(UIViewController *)viewController; 
@end

PEInteractiveTransition.m

#import "PEInteractiveTransition.h"

@interface PEInteractiveTransition()

/** 傳入的ViewController */
@property (nonatomic, weak)UIViewController *vc;
/** 手勢方向 */
@property (nonatomic, assign)PEInteractiveTransitionGestureDirection direction;
/** 手勢類型 */
@property (nonatomic, assign)PEInteractiveTransitionType type;

@end
@implementation PEInteractiveTransition

+ (instancetype)interactiveTransitionWithTransitionType:(PEInteractiveTransitionType)type GestureDirection:(PEInteractiveTransitionGestureDirection)direction
{
    return [[self alloc] initWithTransitionType:type GestureDirection:direction];
}

- (instancetype)initWithTransitionType:(PEInteractiveTransitionType)type GestureDirection:(PEInteractiveTransitionGestureDirection)direction{
    self = [super init];
    if (self) {
        _direction = direction;
        _type = type;
    }
    return self;
}

- (void)addPanGestureForViewController:(UIViewController *)viewController
{
    UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
    self.vc = viewController;
    [viewController.view addGestureRecognizer:pan];
}
/**
 *  手勢過渡過程
 *
 *  @param pan 添加的手勢
 */
- (void)handleGesture:(UIPanGestureRecognizer *)pan
{
    //手勢百分比 初始化為0  向上和向右滑動 距離是負(fù)的 所以前面加負(fù)號 這樣負(fù)負(fù)得正
    CGFloat persent = 0;
    switch (_direction) {
        case PEInteractiveTransitionGestureDirectionUp:{
            CGFloat transitionY = -[pan translationInView:pan.view].y;
            persent = transitionY / pan.view.frame.size.width;
        }
            break;
        case PEInteractiveTransitionGestureDirectionRight:{
            CGFloat transitionR = [pan translationInView:pan.view].x;
            persent = transitionR / pan.view.frame.size.width;
        }
            break;
        case PEInteractiveTransitionGestureDirectionDown:{
            CGFloat transitionD = [pan translationInView:pan.view].y;
            persent = transitionD / pan.view.frame.size.width;
        }
            break;
        case PEInteractiveTransitionGestureDirectionLeft:{
            CGFloat transitionL = -[pan translationInView:pan.view].x;
            persent = transitionL / pan.view.frame.size.width;
        }
            break;
            
        default:
            break;
    }
    switch (pan.state) {
        case UIGestureRecognizerStateBegan:{
            //手勢開始的時候標(biāo)記手勢狀態(tài),并開始相應(yīng)的事件
            self.interation = YES;
            [self startGesture];
        }
            break;
        case UIGestureRecognizerStateChanged:{
            //手勢過程中,通過updateInteractiveTransition設(shè)置pop過程進(jìn)行的百分比
            [self updateInteractiveTransition:persent];
        }
            break;
        case UIGestureRecognizerStateEnded:{
            //手勢完成后結(jié)束標(biāo)記并且判斷移動的距離是否過半,過則finishInteractiveTransition完成轉(zhuǎn)場操作,否則取消轉(zhuǎn)場操作
            self.interation = NO;
            if (persent > 0.5) {
                [self finishInteractiveTransition];
            }else {
                [self cancelInteractiveTransition];
            }
        }
            break;
            
        default:
            break;
    }
}

- (void)startGesture
{
    switch (_type) {
        case PEInteractiveTransitionTypePresent:{
            if (_presentConfig) {
                _presentConfig();
            }
        }
            break;
        case PEInteractiveTransitionTypeDismiss:{
            [_vc dismissViewControllerAnimated:YES completion:nil];
        }
            break;
        case PEInteractiveTransitionTypePush:{
            if (_pushConfig) {
                _pushConfig();
            }
        }
            break;
        case PEInteractiveTransitionTypePop:{
            [_vc.navigationController popViewControllerAnimated:YES];
        }
            break;
            
        default:
            break;
    }
}

@end

3.創(chuàng)建神奇效果的具體動畫代理
PENaviTransition.h

typedef NS_ENUM(NSUInteger, PENaviTransitionType){
    PENaviTransitionTypePush = 0,
    PENaviTransitionTypePop
};

@interface PENaviTransition : NSObject<UIViewControllerAnimatedTransitioning>


+ (instancetype)transitionWithType:(PENaviTransitionType)type;
- (instancetype)initWithTransitionType:(PENaviTransitionType)type;

@end

PENaviTransition.m主要實(shí)現(xiàn)一個動畫時長方法和一個push動畫和一個pop動畫

/**
 *  動畫時長
 */
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
    return 0.75;
}
/**
 *  如何執(zhí)行過渡動畫
 */
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    switch (_type) {
        case PENaviTransitionTypePush:
            [self doPushAnimation:transitionContext];
            break;
        case PENaviTransitionTypePop:
            [self doPopAnimation:transitionContext];
            break;
            
        default:
            break;
    }
}

/**
 *  執(zhí)行push過渡動畫
 */
- (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext
{
    PEMagicMoveController *fromVC = (PEMagicMoveController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    PEMagicMovePushController *toVC = (PEMagicMovePushController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    //拿到當(dāng)前點(diǎn)擊的cell的imageView
    PEMagicMoveCell *cell = (PEMagicMoveCell *)[fromVC.collectionView cellForItemAtIndexPath:fromVC.clickIndex];
    UIView *containerView = [transitionContext containerView];
    UIView *tempView = [cell.imageV snapshotViewAfterScreenUpdates:NO];
    //將點(diǎn)擊的cell截圖作為臨時View的內(nèi)容 并將坐標(biāo)系轉(zhuǎn)化成push控制器種的坐標(biāo)
    tempView.frame = [cell.imageV convertRect:cell.imageV.bounds toView:containerView];
    //設(shè)置動畫前的各個控件的狀態(tài)
    cell.imageV.hidden = YES;
    toVC.view.alpha = 0;
    toVC.imageView.hidden = YES;
    //tempView添加到containerView中保證在最前方,所以后添加
    [containerView addSubview:toVC.view];
    [containerView addSubview:tempView];
    //開始push動畫
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.55 initialSpringVelocity:1/0.55 options:0 animations:^{
        //臨時View(也就是截取cell大小)的frame變成和push出來view種的imageView大小一樣
        tempView.frame = [toVC.imageView convertRect:toVC.imageView.bounds toView:containerView];
        //push控制器由透明為0變?yōu)?
        toVC.view.alpha = 1;
    } completion:^(BOOL finished) {
        tempView.hidden = YES;
        toVC.imageView.hidden = NO;
        //如果動畫過渡取消了就標(biāo)記不完成,否則標(biāo)記完成,這里可以直接寫YES,如果有手勢過渡才需要判斷,必須標(biāo)記,否則系統(tǒng)不會完成動畫,會出現(xiàn)無法交互等bug
        [transitionContext completeTransition:YES];
    }];
}
/**
 *  執(zhí)行pop過渡動畫
 */
- (void)doPopAnimation:(id<UIViewControllerContextTransitioning>)transitionContext
{
    PEMagicMovePushController *fromVC = (PEMagicMovePushController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    PEMagicMoveController *toVC = (PEMagicMoveController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    PEMagicMoveCell *cell = (PEMagicMoveCell *)[toVC.collectionView cellForItemAtIndexPath:toVC.clickIndex];
    UIView *containerView = [transitionContext containerView];
    //這里的lastObject就是push適合初始化的那個tempView
    UIView *tempView = containerView.subviews.lastObject;
    //設(shè)置動畫前狀態(tài)
    cell.imageV.hidden = YES;       //列表頁面的cell先隱藏
    fromVC.imageView.hidden = YES;  //當(dāng)前頁面的imageView也進(jìn)行隱藏
    tempView.hidden = NO;           //最上面的臨時View顯示
    [containerView insertSubview:toVC.view atIndex:0];
    //開始pop動畫
    [UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:.55 initialSpringVelocity:1/0.55 options:0 animations:^{
        //將臨時View的坐標(biāo)和大小變成新坐標(biāo)系中列表cell的坐標(biāo)和大小
        tempView.frame = [cell.imageV convertRect:cell.imageV.bounds toView:containerView];
        //同時詳情頁面隱藏
        fromVC.view.alpha = 0;
    } completion:^(BOOL finished) {
        //加入了手勢判斷
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        if ([transitionContext transitionWasCancelled]) {
            //手勢取消了,原來隱藏的imageView要顯示出來
            tempView.hidden = YES;
            fromVC.imageView.hidden = NO;
        }else{
            //手勢成功
            cell.imageV.hidden = NO;        //列表點(diǎn)擊的cell要顯示
            [tempView removeFromSuperview]; //臨時的要去除 因?yàn)橄乱淮螘匦律?不會產(chǎn)生冗余
        }
    }];
}

4.現(xiàn)在可以在具體進(jìn)行跳轉(zhuǎn)的頁面vc1進(jìn)行使用

#pragma mark <UICollectionViewDelegate>
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
    //記錄點(diǎn)擊的是列表的第幾個 這樣在返回動畫的時候能用上
    _clickIndex = indexPath;
    PEMagicMovePushController *vc = [[PEMagicMovePushController alloc] init];
    //設(shè)置導(dǎo)航控制器的代理為推出的控制器,可以達(dá)到自定義不同控制器推出效果的目的
    self.navigationController.delegate = vc;
    [self.navigationController pushViewController:vc animated:YES];
}

5.在push出的頁面vc2進(jìn)行過渡動畫的監(jiān)聽和實(shí)現(xiàn)

//返回手勢過渡管理對象
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    //分pop和push兩種情況分別返回動畫過渡代理相應(yīng)不同的動畫操作
    return [PENaviTransition transitionWithType:operation == UINavigationControllerOperationPush ? PENaviTransitionTypePush : PENaviTransitionTypePop];
}

//返回轉(zhuǎn)場動畫過渡管理對象
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
    //如果是手動出發(fā)則返回我們的UIPercentDrivenInteractiveTransition對象
    return _interactiveTransition.interation ? _interactiveTransition : nil;
}

6.完成,就是下面的效果

神奇效果.gif

DEMO TWO: 彈性present動畫

1.創(chuàng)建兩個控制器,手勢管理類和DEMO ONE是同一個
2.自己具體的動畫過渡實(shí)現(xiàn)presentAnimationdismissAnimation兩個方法

/**
 *  presentAnimation
 */
- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContent
{
    UIViewController *toVC = [transitionContent viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromVC = [transitionContent viewControllerForKey:UITransitionContextFromViewControllerKey];
    //snapshotViewAfterScreenUpdates:可以對某個試圖截圖,我們采用對這個截圖做動畫代替直接對toVC做動畫,因?yàn)樵谑謩葸^渡中直接使用toVC動畫會和手勢有沖突,如果不需要實(shí)現(xiàn)手勢的話,就可以不是用截圖了
    UIView *tempView = [fromVC.view snapshotViewAfterScreenUpdates:NO];
    tempView.frame = fromVC.view.frame;
    //因?yàn)閷貓D做動畫,fromVC開始是隱藏的
    fromVC.view.hidden = YES;
    //containerView:如果要對視圖做轉(zhuǎn)場動畫,視圖就必須加入containerView中才能進(jìn)行,可以理解為containerView管理所有做轉(zhuǎn)場動畫的視圖
    UIView *containerView = [transitionContent containerView];
    //將截圖視圖和toVC的view都加入containerView中 截圖視圖是fromVC所以先放 先放的在下面
    [containerView addSubview:tempView];
    [containerView addSubview:toVC.view];
    //設(shè)置toVC的frame,因?yàn)槭莗resent出來不是全屏,而且在底部,如果不設(shè)置默認(rèn)是整個屏幕,這里的containerView的frame就是整個屏幕
    toVC.view.frame = CGRectMake(0, containerView.height, containerView.width, 500);
    //開始動畫,使用產(chǎn)生彈簧效果的方法
    [UIView animateWithDuration:[self transitionDuration:transitionContent] delay:0.0 usingSpringWithDamping:0.55 initialSpringVelocity:1.0 / 0.55 options:0 animations:^{
        //1.讓toVC向上移動 向上是負(fù)的
        toVC.view.transform = CGAffineTransformMakeTranslation(0, -500);
        //2.讓截圖視圖縮小即可
        tempView.transform = CGAffineTransformMakeScale(0.85, 0.85);
        
    } completion:^(BOOL finished) {
        //使用如下代碼標(biāo)記整個轉(zhuǎn)場過程是否正常完成[transitionContext transitionWasCancelled]代表手勢是否取消了,如果取消了就傳NO表示轉(zhuǎn)場失敗,反之亦然,如果不是用手勢的話直接傳YES也是可以的,我們必須標(biāo)記轉(zhuǎn)場的狀態(tài),系統(tǒng)才知道處理轉(zhuǎn)場后的操作,否者認(rèn)為你一直還在,會出現(xiàn)無法交互的情況
        [transitionContent completeTransition:![transitionContent transitionWasCancelled]];
        //轉(zhuǎn)場失敗后的處理
        if ([transitionContent transitionWasCancelled])
        {
            //失敗后復(fù)原動畫開始是的樣子
            //1.把fromVC顯示出來
            fromVC.view.hidden = NO;
            //2.移除截圖視圖,下次會重新生成
            [tempView removeFromSuperview];
        }
    }];
}

/**
 *  dismissAnimation
 */
- (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContent
{
    UIViewController *fromVC = [transitionContent viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContent viewControllerForKey:UITransitionContextToViewControllerKey];
    
    //參照present動畫的邏輯,present成功后,containerView的第一個子視圖就是截圖視圖,我們將其取出做動畫
    UIView *containerView = [transitionContent containerView];
    UIView *tempView = containerView.subviews[0];
    //開始動畫
    [UIView animateWithDuration:[self transitionDuration:transitionContent] delay:0.0 usingSpringWithDamping:0.55 initialSpringVelocity:1 / 0.55 options:0 animations:^{
        //present使用的是transform,只需要恢復(fù)就可以了
        fromVC.view.transform = CGAffineTransformIdentity;
        tempView.transform = CGAffineTransformIdentity;
    } completion:^(BOOL finished) {
        if ([transitionContent transitionWasCancelled]) {
            //標(biāo)記失敗
            [transitionContent completeTransition:NO];
        }else{
            //成功后,標(biāo)記成功,讓toVC顯示出來,并移除截圖視圖
            [transitionContent completeTransition:YES];
            toVC.view.hidden = NO;
            [tempView removeFromSuperview];
        }
    }];
}

3.vc1中進(jìn)行調(diào)用

    PEElasticOneController *elasticVC = [PEElasticOneController new];
    elasticVC.delegate = self;
    [self presentViewController:elasticVC animated:YES completion:nil];

4.vc2中監(jiān)聽

#pragma mark - ---UIViewControllerTransitioningDelegate---
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source
{
    return [PEElasticTransition transitionWithTransitionType:PEElasticTransitionTypePresent];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed
{
    return [PEElasticTransition transitionWithTransitionType:PEElasticTransitionTypeDismiss];
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id<UIViewControllerAnimatedTransitioning>)animator
{
    PEInteractiveTransition *interactivePresent = [self.delegate interactiveTransitionForPresent];
    return interactivePresent.interation ? interactivePresent : nil;
}
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal:(id<UIViewControllerAnimatedTransitioning>)animator
{
    return self.interactiveTransition.interation ? self.interactiveTransition : nil;
}

這樣就基本完成了

彈性present.gif

DEMO THREE:翻頁push效果

1.其它和上面的相似,直接來看核心的過渡動畫實(shí)現(xiàn):

/**
 *  自定義push動畫
 */
- (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    //對當(dāng)前view截圖 然后作為動畫對象
    UIView *tempV = [fromVC.view snapshotViewAfterScreenUpdates:NO];
    tempV.frame = fromVC.view.frame;
    UIView *containerV = [transitionContext containerView];
    //臨時的放最上面 做過渡
    [containerV addSubview:toVC.view];
    [containerV addSubview:tempV];
    fromVC.view.hidden = YES;
    //設(shè)置anchorPoint 選擇的支點(diǎn)
    [tempV setAnchorPoint:CGPointMake(0, 0.5)];
    CATransform3D transform3d = CATransform3DIdentity;
    //m34(透視效果,要操作的這個對象要有旋轉(zhuǎn)的角度,否則沒有效果。正直/負(fù)值都有意義);
    transform3d.m34 = -0.002;
    containerV.layer.sublayerTransform = transform3d;
    //增加陰影
    CAGradientLayer *fromGradient = [CAGradientLayer layer];
    fromGradient.frame = fromVC.view.bounds;
    fromGradient.colors = @[(id)[UIColor blackColor].CGColor,(id)[UIColor blackColor].CGColor];
    fromGradient.startPoint = CGPointMake(0.0, 0.5);
    fromGradient.endPoint = CGPointMake(0.8, 0.5);
    UIView *fromShadow = [[UIView alloc] initWithFrame:fromVC.view.bounds];
    fromShadow.backgroundColor = [UIColor clearColor];
    [fromShadow.layer insertSublayer:fromGradient atIndex:1];
    fromShadow.alpha = 0.0;
    [tempV addSubview:fromShadow];
    CAGradientLayer *toGradient = [CAGradientLayer layer];
    toGradient.frame = fromVC.view.bounds;
    toGradient.colors = @[(id)[UIColor blackColor].CGColor,(id)[UIColor blackColor].CGColor];
    toGradient.startPoint = CGPointMake(0.0, 0.5);
    toGradient.endPoint = CGPointMake(0.8, 0.5);
    UIView *toShadow = [[UIView alloc] initWithFrame:fromVC.view.bounds];
    toShadow.backgroundColor = [UIColor clearColor];
    [toShadow.layer insertSublayer:toGradient atIndex:1];
    toShadow.alpha = 1.0;
    [toVC.view addSubview:toShadow];
    //開始動畫
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        //沿Y軸旋轉(zhuǎn) 逆時針180°
        tempV.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1, 0);
        //當(dāng)前的由亮變暗 push的VC由暗變亮
        fromShadow.alpha = 1.0;
        toShadow.alpha = 0.0;
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
        //動畫取消的操作
        if ([transitionContext transitionWasCancelled]){
            [tempV removeFromSuperview];
            fromVC.view.hidden = NO;
        }
    }];
}

/**
 *  自定義pop動畫
 */
- (void)doPopAnimation:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIView *containerV = [transitionContext containerView];
    UIView *tempV = containerV.subviews.lastObject;
    [containerV addSubview:toVC.view];
    //開始動畫
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        //臨時圖層恢復(fù)原狀
        tempV.layer.transform = CATransform3DIdentity;
        fromVC.view.subviews.lastObject.alpha = 1.0;
        tempV.subviews.lastObject.alpha = 0.0;
    } completion:^(BOOL finished) {
        if ([transitionContext transitionWasCancelled]){
            [transitionContext completeTransition:NO];
        }else{
            [transitionContext completeTransition:YES];
            [tempV removeFromSuperview];
            toVC.view.hidden = NO;
        }
    }];
}

2.手勢加上的效果如下:

![Uploading 翻頁效果_161246.gif . . .]

DEMO FOUR:圓點(diǎn)擴(kuò)散present效果

1.其它和上面相似,來看核心過渡的動畫實(shí)現(xiàn)

- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UINavigationController *fromVC = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIView *containerV = [transitionContext containerView];
    PECircleSpreadController *temp = fromVC.viewControllers.lastObject;
    [containerV addSubview:toVC.view];
    //畫兩個圓路徑
    UIBezierPath *startCircle = [UIBezierPath bezierPathWithOvalInRect:temp.buttonFrame];
    //始終用最大的x值和y值的平方根 來作為圓的半徑 這樣保證了圓點(diǎn)中心到最遠(yuǎn)的邊的距離作為半徑
    CGFloat x = MAX(temp.buttonFrame.origin.x, containerV.frame.size.width - temp.buttonFrame.origin.x);
    CGFloat y = MAX(temp.buttonFrame.origin.y, containerV.frame.size.height - temp.buttonFrame.origin.y);
    CGFloat radius = sqrtf(pow(x, 2) + pow(y, 2));
    //圓心 半徑 開始角度 結(jié)束角度M_PI*2是360° 是否順時針
    UIBezierPath *endCircle = [UIBezierPath bezierPathWithArcCenter:containerV.center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
    
    //創(chuàng)建CAShapLayer進(jìn)行遮蓋
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.path = endCircle.CGPath;
    //將maskLayer作為toVC的遮蓋
    toVC.view.layer.mask = maskLayer;
    //創(chuàng)建路徑動畫
    CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    //動畫是添加到layer上的,所以必須為CGPath,再將CGPath橋接為OC對象
    maskLayerAnimation.fromValue = (__bridge id)(startCircle.CGPath);
    maskLayerAnimation.toValue = (__bridge id)(endCircle.CGPath);
    maskLayerAnimation.duration = [self transitionDuration:transitionContext];
    maskLayerAnimation.delegate = self;
    maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];   //動畫速度: 先慢 后慢 中間快
    [maskLayerAnimation setValue:transitionContext forKey:@"transitionContext"];
    [maskLayer addAnimation:maskLayerAnimation forKey:@"path"];    
}

- (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext
{
    PECircleSpreadPresentedController *fromVC = (PECircleSpreadPresentedController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UINavigationController *toVC = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    PECircleSpreadController *temp = toVC.viewControllers.lastObject;
    UIView *containerV = [transitionContext containerView];
    //畫兩個圓的路徑
    CGFloat radius = sqrtf(pow(containerV.frame.size.height, 2) + pow(containerV.frame.size.width, 2))/2.0;
    UIBezierPath *startCircle = [UIBezierPath bezierPathWithArcCenter:containerV.center radius:radius startAngle:0 endAngle:M_PI*2 clockwise:YES];
    temp.buttonFrame = fromVC.presentButtonFrame;
    UIBezierPath *endCircle = [UIBezierPath bezierPathWithOvalInRect:temp.buttonFrame];
    //創(chuàng)建CAShapeLayer進(jìn)行覆蓋
    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.fillColor = [UIColor greenColor].CGColor;
    maskLayer.path = endCircle.CGPath;
    fromVC.view.layer.mask = maskLayer;
    //創(chuàng)建路徑動畫
    CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
    maskLayerAnimation.delegate = self;
    maskLayerAnimation.fromValue = (__bridge id)(startCircle.CGPath);
    maskLayerAnimation.toValue = (__bridge id)(endCircle.CGPath);
    maskLayerAnimation.duration = [self transitionDuration:transitionContext];
    maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
    [maskLayerAnimation setValue:transitionContext forKey:@"transitionContext"];
    [maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
    
}

- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag
{ 
    switch (_type) {
        case PECircleSpreadTransitionTypePresent:{
            id<UIViewControllerContextTransitioning> transitionContext = [anim valueForKey:@"transitionContext"];
            [transitionContext completeTransition:YES];
        }
            break;
        case PECircleSpreadTransitionTypeDismiss:{
            id<UIViewControllerContextTransitioning> transitionContext = [anim valueForKey:@"transitionContext"];
            [transitionContext completeTransition:![transitionContext transitionWasCancelled]];
            if ([transitionContext transitionWasCancelled]) {
                //去掉遮擋層
                [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil;
            }
        }
            break;
            
        default:
            break;
    }
}

注意:最后animationDidStop的代理方法中處理到動畫的完成邏輯.
2.最后的效果如下:

小圓點(diǎn)擴(kuò)散.gif

總結(jié)
1、關(guān)于:self.modalPresentationStyle = UIModalPresentationCustom;我查看了視圖層級后發(fā)現(xiàn),如果使用了Custom,在present動畫完成的時候,presentingView也就是demo one中的vc1的view會從containerView中移除,只是移除,并未銷毀,此時還被持有著(dismiss后還得回來呢!),如果設(shè)置custom,那么present完成后,它一直都在containerView中,只是在最后面,所以需不需要設(shè)置custom可以看動畫完成后的情況,是否還需要看見presentingViewController,但是記住如果沒有設(shè)置custom,在disMiss的動畫邏輯中,要把它加回containerView中,不然就不在咯~!
2、感覺寫了好多東西,其實(shí)只要弄懂了轉(zhuǎn)場的邏輯,其實(shí)就只需要寫動畫的邏輯就行了,其他東西都是固定的,而且蘋果提供的這種控制轉(zhuǎn)場的方式可充分解耦,除了寫的手勢過渡管理可以拿到任何地方使用,所有的動畫過渡管理者都可以很輕松的復(fù)用到其他轉(zhuǎn)場中,都不用分是何種轉(zhuǎn)場,demo沒有寫標(biāo)簽控制器的轉(zhuǎn)場,實(shí)現(xiàn)方法也是完全類似的,大家可以嘗試一下,四個demo的github地址:自定義轉(zhuǎn)場動畫demo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 更新,更簡單的自定義轉(zhuǎn)場集成! 幾句代碼快速集成自定義轉(zhuǎn)場效果+ 全手勢驅(qū)動 寫在前面 這兩天閑下來好好的研究了一...
    wazrx閱讀 73,946評論 84 584
  • 路漫漫其修遠(yuǎn)兮,吾將上下而求索 前記 想研究自定義轉(zhuǎn)場動畫很久了,時間就像海綿,擠一擠還是有的,花了差不多有10天...
    半笑半醉間閱讀 7,519評論 10 51
  • iOS7.0后蘋果提供了自定義轉(zhuǎn)場動畫的API,利用這些API我們可以改變 push和pop(navigation...
    薛定喵的鵝閱讀 17,945評論 1 37
  • iOS 7 以協(xié)議的方式開放了自定義轉(zhuǎn)場的 API,協(xié)議的好處是不再拘泥于具體的某個類,只要是遵守該協(xié)議的對象都能...
    iceMaple閱讀 2,013評論 0 13
  • 那是我第一次、也是至今唯一一次,見到父親的眼淚。 那年,由于一系列原因,我要從山東到東北去上大學(xué),開學(xué)前幾天,父母...
    心有余響閱讀 204評論 0 3