通過這幾天的學習,我嘗試實現了四個效果,廢話不多說,先上效果圖:
DEMO ONE:一個神奇移動效果push動畫,支持手勢pop
DEMO TWO:一個彈性的present動畫,支持手勢present和dismiss
DEMO THREE:一個翻頁push效果,支持手勢PUSH和POP
DEMO FOUR:一個小圓點擴散present效果,支持手勢dimiss
準備
從iOS7開始,蘋果提供了自定義轉場的API,模態推送present和dismiss,導航控制器的push和pop,標簽控制器的控制器切換都可以自定義轉場了.
1.我們需要自定義一個遵循的<UIViewControllerAnimatedTransitioning>
協議的動畫過渡管理對象,并實現兩個必須實現的方法:
// 返回動畫事件
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
// 所有的過渡動畫事務都在這個方法里面完成
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
2.我們還需要自定義一個繼承于UIPercentDrivenInteractiveTransition的手勢過渡管理對象,我把它成為百分比手勢過渡管理對象,因為動畫的過程是通過百分比控制的
3.成為相應的代理,實現相應的代理方法,返回我們前兩步自定義的對象就OK了 !
模態推送需要實現如下4個代理方法,iOS8新的那個方法我暫時還沒有發現它的用處,所以暫不討論.
//返回一個管理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;
導航控制器實現如下2個代理方法
//返回轉場動畫過渡管理對象
- (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);
標簽控制器也有相應的兩個方法
//返回轉場動畫過渡管理對象
- (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.如果看著這些常常的代理方法名頭疼的話,沒關系,先在demo中用起來吧,慢慢就習慣了,其實哪種自定義轉場都只需要這3個步驟,如果不需要手勢控制,步驟2還可以取消,現在就讓我們動手來實現效果吧
Let's go!
DEMO ONE:神奇移動效果
1.我們首先創建2個控制器,為了方便我稱做present操作的為vc1、被present的為vc2,點擊一個控制器上的按鈕可以push出另一個控制器.
2.創建一個手勢過渡管理的類,我這里是PEInteractiveTransition,因為無論哪一種轉場,手勢控制的實質都是一樣的,我干脆就把這個手勢過渡管理的類封裝了一下,具體可以在.h文件里面查看,在接下來的三個轉場效果中我們都可以便捷的是使用它:
PEInteractiveTransition.h
typedef void(^GestureConfig)();
//手勢轉場類型
typedef NS_ENUM(NSUInteger,PEInteractiveTransitionType){
PEInteractiveTransitionTypePresent = 0,
PEInteractiveTransitionTypeDismiss,
PEInteractiveTransitionTypePush,
PEInteractiveTransitionTypePop
};
//手勢方向
typedef NS_ENUM(NSUInteger,PEInteractiveTransitionGestureDirection){
PEInteractiveTransitionGestureDirectionLeft = 0,
PEInteractiveTransitionGestureDirectionRight,
PEInteractiveTransitionGestureDirectionUp,
PEInteractiveTransitionGestureDirectionDown
};
@interface PEInteractiveTransition : UIPercentDrivenInteractiveTransition
/** 記錄是否開始手勢,判斷pop操作是手勢出發還是返回鍵出發 */
@property (nonatomic, assign)BOOL interation;
/** 觸發手勢present的時候config,在config中初始化并present需要彈出的控制器 */
@property (nonatomic, copy)GestureConfig presentConfig;
/** 觸發手勢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 向上和向右滑動 距離是負的 所以前面加負號 這樣負負得正
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:{
//手勢開始的時候標記手勢狀態,并開始相應的事件
self.interation = YES;
[self startGesture];
}
break;
case UIGestureRecognizerStateChanged:{
//手勢過程中,通過updateInteractiveTransition設置pop過程進行的百分比
[self updateInteractiveTransition:persent];
}
break;
case UIGestureRecognizerStateEnded:{
//手勢完成后結束標記并且判斷移動的距離是否過半,過則finishInteractiveTransition完成轉場操作,否則取消轉場操作
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.創建神奇效果的具體動畫代理
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主要實現一個動畫時長方法和一個push動畫和一個pop動畫
/**
* 動畫時長
*/
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext
{
return 0.75;
}
/**
* 如何執行過渡動畫
*/
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
switch (_type) {
case PENaviTransitionTypePush:
[self doPushAnimation:transitionContext];
break;
case PENaviTransitionTypePop:
[self doPopAnimation:transitionContext];
break;
default:
break;
}
}
/**
* 執行push過渡動畫
*/
- (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext
{
PEMagicMoveController *fromVC = (PEMagicMoveController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
PEMagicMovePushController *toVC = (PEMagicMovePushController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//拿到當前點擊的cell的imageView
PEMagicMoveCell *cell = (PEMagicMoveCell *)[fromVC.collectionView cellForItemAtIndexPath:fromVC.clickIndex];
UIView *containerView = [transitionContext containerView];
UIView *tempView = [cell.imageV snapshotViewAfterScreenUpdates:NO];
//將點擊的cell截圖作為臨時View的內容 并將坐標系轉化成push控制器種的坐標
tempView.frame = [cell.imageV convertRect:cell.imageV.bounds toView:containerView];
//設置動畫前的各個控件的狀態
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變為1
toVC.view.alpha = 1;
} completion:^(BOOL finished) {
tempView.hidden = YES;
toVC.imageView.hidden = NO;
//如果動畫過渡取消了就標記不完成,否則標記完成,這里可以直接寫YES,如果有手勢過渡才需要判斷,必須標記,否則系統不會完成動畫,會出現無法交互等bug
[transitionContext completeTransition:YES];
}];
}
/**
* 執行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;
//設置動畫前狀態
cell.imageV.hidden = YES; //列表頁面的cell先隱藏
fromVC.imageView.hidden = YES; //當前頁面的imageView也進行隱藏
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的坐標和大小變成新坐標系中列表cell的坐標和大小
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; //列表點擊的cell要顯示
[tempView removeFromSuperview]; //臨時的要去除 因為下一次會重新生成 不會產生冗余
}
}];
}
4.現在可以在具體進行跳轉的頁面vc1進行使用
#pragma mark <UICollectionViewDelegate>
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
//記錄點擊的是列表的第幾個 這樣在返回動畫的時候能用上
_clickIndex = indexPath;
PEMagicMovePushController *vc = [[PEMagicMovePushController alloc] init];
//設置導航控制器的代理為推出的控制器,可以達到自定義不同控制器推出效果的目的
self.navigationController.delegate = vc;
[self.navigationController pushViewController:vc animated:YES];
}
5.在push出的頁面vc2進行過渡動畫的監聽和實現
//返回手勢過渡管理對象
- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
//分pop和push兩種情況分別返回動畫過渡代理相應不同的動畫操作
return [PENaviTransition transitionWithType:operation == UINavigationControllerOperationPush ? PENaviTransitionTypePush : PENaviTransitionTypePop];
}
//返回轉場動畫過渡管理對象
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController
{
//如果是手動出發則返回我們的UIPercentDrivenInteractiveTransition對象
return _interactiveTransition.interation ? _interactiveTransition : nil;
}
6.完成,就是下面的效果
DEMO TWO: 彈性present動畫
1.創建兩個控制器,手勢管理類和DEMO ONE是同一個
2.自己具體的動畫過渡實現presentAnimation
和dismissAnimation
兩個方法
/**
* presentAnimation
*/
- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContent
{
UIViewController *toVC = [transitionContent viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = [transitionContent viewControllerForKey:UITransitionContextFromViewControllerKey];
//snapshotViewAfterScreenUpdates:可以對某個試圖截圖,我們采用對這個截圖做動畫代替直接對toVC做動畫,因為在手勢過渡中直接使用toVC動畫會和手勢有沖突,如果不需要實現手勢的話,就可以不是用截圖了
UIView *tempView = [fromVC.view snapshotViewAfterScreenUpdates:NO];
tempView.frame = fromVC.view.frame;
//因為對截圖做動畫,fromVC開始是隱藏的
fromVC.view.hidden = YES;
//containerView:如果要對視圖做轉場動畫,視圖就必須加入containerView中才能進行,可以理解為containerView管理所有做轉場動畫的視圖
UIView *containerView = [transitionContent containerView];
//將截圖視圖和toVC的view都加入containerView中 截圖視圖是fromVC所以先放 先放的在下面
[containerView addSubview:tempView];
[containerView addSubview:toVC.view];
//設置toVC的frame,因為是present出來不是全屏,而且在底部,如果不設置默認是整個屏幕,這里的containerView的frame就是整個屏幕
toVC.view.frame = CGRectMake(0, containerView.height, containerView.width, 500);
//開始動畫,使用產生彈簧效果的方法
[UIView animateWithDuration:[self transitionDuration:transitionContent] delay:0.0 usingSpringWithDamping:0.55 initialSpringVelocity:1.0 / 0.55 options:0 animations:^{
//1.讓toVC向上移動 向上是負的
toVC.view.transform = CGAffineTransformMakeTranslation(0, -500);
//2.讓截圖視圖縮小即可
tempView.transform = CGAffineTransformMakeScale(0.85, 0.85);
} completion:^(BOOL finished) {
//使用如下代碼標記整個轉場過程是否正常完成[transitionContext transitionWasCancelled]代表手勢是否取消了,如果取消了就傳NO表示轉場失敗,反之亦然,如果不是用手勢的話直接傳YES也是可以的,我們必須標記轉場的狀態,系統才知道處理轉場后的操作,否者認為你一直還在,會出現無法交互的情況
[transitionContent completeTransition:![transitionContent transitionWasCancelled]];
//轉場失敗后的處理
if ([transitionContent transitionWasCancelled])
{
//失敗后復原動畫開始是的樣子
//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,只需要恢復就可以了
fromVC.view.transform = CGAffineTransformIdentity;
tempView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
if ([transitionContent transitionWasCancelled]) {
//標記失敗
[transitionContent completeTransition:NO];
}else{
//成功后,標記成功,讓toVC顯示出來,并移除截圖視圖
[transitionContent completeTransition:YES];
toVC.view.hidden = NO;
[tempView removeFromSuperview];
}
}];
}
3.vc1中進行調用
PEElasticOneController *elasticVC = [PEElasticOneController new];
elasticVC.delegate = self;
[self presentViewController:elasticVC animated:YES completion:nil];
4.vc2中監聽
#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;
}
這樣就基本完成了
DEMO THREE:翻頁push效果
1.其它和上面的相似,直接來看核心的過渡動畫實現:
/**
* 自定義push動畫
*/
- (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext
{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//對當前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;
//設置anchorPoint 選擇的支點
[tempV setAnchorPoint:CGPointMake(0, 0.5)];
CATransform3D transform3d = CATransform3DIdentity;
//m34(透視效果,要操作的這個對象要有旋轉的角度,否則沒有效果。正直/負值都有意義);
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軸旋轉 逆時針180°
tempV.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1, 0);
//當前的由亮變暗 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:^{
//臨時圖層恢復原狀
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:圓點擴散present效果
1.其它和上面相似,來看核心過渡的動畫實現
- (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值的平方根 來作為圓的半徑 這樣保證了圓點中心到最遠的邊的距離作為半徑
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));
//圓心 半徑 開始角度 結束角度M_PI*2是360° 是否順時針
UIBezierPath *endCircle = [UIBezierPath bezierPathWithArcCenter:containerV.center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
//創建CAShapLayer進行遮蓋
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.path = endCircle.CGPath;
//將maskLayer作為toVC的遮蓋
toVC.view.layer.mask = maskLayer;
//創建路徑動畫
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];
//創建CAShapeLayer進行覆蓋
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillColor = [UIColor greenColor].CGColor;
maskLayer.path = endCircle.CGPath;
fromVC.view.layer.mask = maskLayer;
//創建路徑動畫
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.最后的效果如下:
總結
1、關于:self.modalPresentationStyle = UIModalPresentationCustom;我查看了視圖層級后發現,如果使用了Custom,在present動畫完成的時候,presentingView也就是demo one中的vc1的view會從containerView中移除,只是移除,并未銷毀,此時還被持有著(dismiss后還得回來呢!),如果設置custom,那么present完成后,它一直都在containerView中,只是在最后面,所以需不需要設置custom可以看動畫完成后的情況,是否還需要看見presentingViewController,但是記住如果沒有設置custom,在disMiss的動畫邏輯中,要把它加回containerView中,不然就不在咯~!
2、感覺寫了好多東西,其實只要弄懂了轉場的邏輯,其實就只需要寫動畫的邏輯就行了,其他東西都是固定的,而且蘋果提供的這種控制轉場的方式可充分解耦,除了寫的手勢過渡管理可以拿到任何地方使用,所有的動畫過渡管理者都可以很輕松的復用到其他轉場中,都不用分是何種轉場,demo沒有寫標簽控制器的轉場,實現方法也是完全類似的,大家可以嘗試一下,四個demo的github地址:自定義轉場動畫demo