更新,更簡單的自定義轉場集成!
寫在前面
這兩天閑下來好好的研究了一下自定義轉場,關于這方面的文章網絡上已經很多了,作為新手,我想通過這篇文章把自己這幾天的相關學習心得記錄一下,方便加深印響和以后的回顧,這是我第一寫技術文章,不好之處請諒解,通過這幾天的學習,我嘗試實現了四個效果,廢話不多說,先上效果圖:
DEMO ONE:一個彈性的present動畫,支持手勢present和dismiss
DEMO TWO:一個類似于KeyNote的神奇移動效果push動畫,支持手勢pop
DEMO THREE:一個翻頁push效果,支持手勢PUSH和POP
DEMO FOUR:一個小圓點擴散present效果,支持手勢dimiss
動手前
大家都知道從iOS7開始,蘋果就提供了自定義轉場的API,模態推送present和dismiss、導航控制器push和pop、標簽控制器的控制器切換都可以自定義轉場了,關于過多的理論我就不太多說明了,大家可以先參照onevcat大神的這篇博客:WWDC 2013 Session筆記 - iOS7中的ViewController切換,我想把整個自定義轉場的步驟做個總結:
-
我們需要自定義一個遵循的
<UIViewControllerAnimatedTransitioning>
協議的動畫過渡管理對象,并實現兩個必須實現的方法://返回動畫事件 - (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext; //所有的過渡動畫事務都在這個方法里面完成 - (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
我們還需要自定義一個繼承于
UIPercentDrivenInteractiveTransition
的手勢過渡管理對象,我把它成為百分比手勢過渡管理對象,因為動畫的過程是通過百分比控制的-
成為相應的代理,實現相應的代理方法,返回我們前兩步自定義的對象就OK了 !
模態推送需要實現如下4個代理方法,iOS8新的那個方法我暫時還沒有發現它的用處,所以暫不討論
//返回一個管理prenent動畫過渡的對象 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source; //返回一個管理pop動畫過渡的對象 - (nullable id <UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed; //返回一個管理prenent手勢過渡的對象 - (nullable id <UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation:(id <UIViewControllerAnimatedTransitioning>)animator; //返回一個管理pop動畫過渡的對象 - (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);
如果看著這些常常的代理方法名頭疼的話,沒關系,先在demo中用起來吧,慢慢就習慣了,其實哪種自定義轉場都只需要這3個步驟,如果不需要手勢控制,步驟2還可以取消,現在就讓我們動手來實現效果吧
動手吧!
demo one
1、我們首先創建2個控制器,為了方便我稱做present操作的為vc1、被present的為vc2,點擊一個控制器上的按鈕可以push出另一個控制器
2、 然后我們創建一個過渡動畫管理的類,遵循<UIViewControllerAnimatedTransitioning>
協議,我這里是XWPresentOneTransition
,由于我們要同時管理present和dismiss2個動畫,你可以實現相應的兩個類分別管理兩個動畫,但是我覺得用一個類來管理就好了,看著比較舒服,邏輯也比較緊密,因為present和dismiss的動畫邏輯很類似,寫在一起,可以相互參考,所以我定義了一個枚舉和兩個初始化方法:
XWPresentOneTransition.h
typedef NS_ENUM(NSUInteger, XWPresentOneTransitionType) {
XWPresentOneTransitionTypePresent = 0,//管理present動畫
XWPresentOneTransitionTypeDismiss//管理dismiss動畫
};
@interface XWPresentOneTransition : NSObject<UIViewControllerAnimatedTransitioning>
//根據定義的枚舉初始化的兩個方法
+ (instancetype)transitionWithTransitionType:(XWPresentOneTransitionType)type;
- (instancetype)initWithTransitionType:(XWPresentOneTransitionType)type;
3、 然后再.m文件里面實現必須實現的兩個代理方法
@implementation XWPresentOneTransition
- (NSTimeInterval)transitionDuration:(id<UIViewControllerContextTransitioning>)transitionContext{
return 0.5;
}
- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext{
//為了將兩種動畫的邏輯分開,變得更加清晰,我們分開書寫邏輯,
switch (_type) {
case XWPresentOneTransitionTypePresent:
[self presentAnimation:transitionContext];
break;
case XWPresentOneTransitionTypeDismiss:
[self dismissAnimation:transitionContext];
break;
}
}
//實現present動畫邏輯代碼
- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
}
//實現dismiss動畫邏輯代碼
- (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
}
4、 設置vc2的transitioningDelegate
,我就設為它自己咯,我實在vc2的init方法中設置的,并實現代理方法
- (instancetype)init
{
self = [super init];
if (self) {
self.transitioningDelegate = self;
//為什么要設置為Custom,在最后說明.
self.modalPresentationStyle = UIModalPresentationCustom;
}
return self;
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForPresentedController:(UIViewController *)presented presentingController:(UIViewController *)presenting sourceController:(UIViewController *)source{
//這里我們初始化presentType
return [XWPresentOneTransition transitionWithTransitionType:XWPresentOneTransitionTypePresent];
}
- (id<UIViewControllerAnimatedTransitioning>)animationControllerForDismissedController:(UIViewController *)dismissed{
//這里我們初始化dismissType
return [XWPresentOneTransition transitionWithTransitionType:XWPresentOneTransitionTypeDismiss];
}
5、 至此我們所有的準備工作就做好了,下面只需要專心在presentAnimation:
方法和dismissAnimation
方法中實現動畫邏輯就OK了,先看presentAnimation:
- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{`
//通過viewControllerForKey取出轉場前后的兩個控制器,這里toVC就是vc1、fromVC就是vc2
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
//snapshotViewAfterScreenUpdates可以對某個視圖截圖,我們采用對這個截圖做動畫代替直接對vc1做動畫,因為在手勢過渡中直接使用vc1動畫會和手勢有沖突, 如果不需要實現手勢的話,就可以不是用截圖視圖了,大家可以自行嘗試一下
UIView *tempView = [fromVC.view snapshotViewAfterScreenUpdates:NO];
tempView.frame = fromVC.view.frame;
//因為對截圖做動畫,vc1就可以隱藏了
fromVC.view.hidden = YES;
//這里有個重要的概念containerView,如果要對視圖做轉場動畫,視圖就必須要加入containerView中才能進行,可以理解containerView管理著所有做轉場動畫的視圖
UIView *containerView = [transitionContext containerView];
//將截圖視圖和vc2的view都加入ContainerView中
[containerView addSubview:tempView];
[containerView addSubview:toVC.view];
//設置vc2的frame,因為這里vc2present出來不是全屏,且初始的時候在底部,如果不設置frame的話默認就是整個屏幕咯,這里containerView的frame就是整個屏幕
toVC.view.frame = CGRectMake(0, containerView.height, containerView.width, 400);
//開始動畫吧,使用產生彈簧效果的動畫API
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0 usingSpringWithDamping:0.55 initialSpringVelocity:1.0 / 0.55 options:0 animations:^{
//首先我們讓vc2向上移動
toVC.view.transform = CGAffineTransformMakeTranslation(0, -400);
//然后讓截圖視圖縮小一點即可
tempView.transform = CGAffineTransformMakeScale(0.85, 0.85);
} completion:^(BOOL finished) {
//使用如下代碼標記整個轉場過程是否正常完成[transitionContext transitionWasCancelled]代表手勢是否取消了,如果取消了就傳NO表示轉場失敗,反之亦然,如果不用手勢present的話直接傳YES也是可以的,但是無論如何我們都必須標記轉場的狀態,系統才知道處理轉場后的操作,否者認為你一直還在轉場中,會出現無法交互的情況,切記!
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
//轉場失敗后的處理
if ([transitionContext transitionWasCancelled]) {
//失敗后,我們要把vc1顯示出來
fromVC.view.hidden = NO;
//然后移除截圖視圖,因為下次觸發present會重新截圖
[tempView removeFromSuperview];
}
}];
}
再看dismissAnimation
方法
- (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
//注意在dismiss的時候fromVC就是vc2了,toVC才是VC1了,注意這個關系
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//參照present動畫的邏輯,present成功后,containerView的最后一個子視圖就是截圖視圖,我們將其取出準備動畫
UIView *tempView = [transitionContext containerView].subviews[0];
//動畫吧
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//因為present的時候都是使用的transform,這里的動畫只需要將transform恢復就可以了
fromVC.view.transform = CGAffineTransformIdentity;
tempView.transform = CGAffineTransformIdentity;
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
//失敗了標記失敗
[transitionContext completeTransition:NO];
}else{
//如果成功了,我們需要標記成功,同時讓vc1顯示出來,然后移除截圖視圖,
[transitionContext completeTransition:YES];
toVC.view.hidden = NO;
[tempView removeFromSuperview];
}
}];
}
6、如果不需要手勢控制,這個轉場就算完成了,下面我們來添加手勢,首先創建一個手勢過渡管理的類,我這里是XWInteractiveTransition
,因為無論哪一種轉場,手勢控制的實質都是一樣的,我干脆就把這個手勢過渡管理的類封裝了一下,具體可以在.h文件里面查看,在接下來的三個轉場效果中我們都可以便捷的是使用它 .m文件說明
//通過這個方法給控制器的View添加相應的手勢
- (void)addPanGestureForViewController:(UIViewController *)viewController{
UIPanGestureRecognizer *pan = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handleGesture:)];
//將傳入的控制器保存,因為要利用它觸發轉場操作
self.vc = viewController;
[viewController.view addGestureRecognizer:pan];
}
//關鍵的手勢過渡的過程
- (void)handleGesture:(UIPanGestureRecognizer *)panGesture{
//persent是根據panGesture的移動距離獲取的,這里就不說明了,可具體去代碼中查看
switch (panGesture.state) {
case UIGestureRecognizerStateBegan:
//手勢開始的時候標記手勢狀態,并開始相應的事件,它的作用在使用這個類的時候說明
self.interation = YES;
//手勢開始是觸發對應的轉場操作,方法代碼在后面
[self startGesture];
break;
case UIGestureRecognizerStateChanged:{
//手勢過程中,通過updateInteractiveTransition設置轉場過程進行的百分比,然后系統會根據百分比自動布局控件,不用我們控制了
[self updateInteractiveTransition:persent];
break;
}
case UIGestureRecognizerStateEnded:{
//手勢完成后結束標記并且判斷移動距離是否過半,過則finishInteractiveTransition完成轉場操作,否者取消轉場操作,轉場失敗
self.interation = NO;
if (persent > 0.5) {
[self finishInteractiveTransition];
}else{
[self cancelInteractiveTransition];
}
break;
}
default:
break;
}
}
//觸發對應轉場操作的代碼如下,根據type(type是我自定義的枚舉值)我們去判斷是觸發哪種操作,對于push和present由于要傳入需要push和present的控制器,為了解耦,我用block把這個操作交個控制器去做了,讓這個手勢過渡管理者可以充分被復用
- (void)startGesture{
switch (_type) {
case XWInteractiveTransitionTypePresent:{
if (_presentConifg) {
_presentConifg();
}
}
break;
case XWInteractiveTransitionTypeDismiss:
[_vc dismissViewControllerAnimated:YES completion:nil];
break;
case XWInteractiveTransitionTypePush:{
if (_pushConifg) {
_pushConifg();
}
}
break;
case XWInteractiveTransitionTypePop:
[_vc.navigationController popViewControllerAnimated:YES];
break;
}
}
7、 手勢過渡管理者就算完畢了,這個手勢管理者可以用到其他任何的模態和導航控制器轉場中,以后都不用在寫了,現在把他用起來,在vc2和vc1中創建相應的手勢過渡管理者,并放到相應的代理方法去返回它
//創建dismiss手勢過渡管理者,present的手勢過渡要在vc1中創建,因為present的手勢是加載vc1的view上的,我選擇通過代理吧vc1中創建的手勢過渡管理者傳過來
self.interactiveDismiss = [XWInteractiveTransition interactiveTransitionWithTransitionType:XWInteractiveTransitionTypeDismiss GestureDirection:XWInteractiveTransitionGestureDirectionDown];
[self.interactiveDismiss addPanGestureForViewController:self];
[_interactivePush addPanGestureForViewController:self.navigationController];
//返回dissmiss的手勢過渡管理
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForDismissal: (id<UIViewControllerAnimatedTransitioning>)animator{
//在沒有用手勢觸發的dismiss的時候需要傳nil,否者無法點擊dimiss,所以interation就是用來判斷是否是手勢觸發轉場的
return _interactiveDismiss.interation ? _interactiveDismiss : nil;
}
//返回present的手勢管理,這個手勢管理者是在vc1中創建的,我用代理傳過來的
- (id<UIViewControllerInteractiveTransitioning>)interactionControllerForPresentation: (id<UIViewControllerAnimatedTransitioning>)animator{
XWInteractiveTransition *interactivePresent = [_delegate interactiveTransitionForPresent];
return interactivePresent.interation ? interactivePresent : nil;
}
8、 終于完成了,再來看一下效果,是不是還不錯!
DEMO TWO
1、 創建動畫過渡管理者的代碼就不重復說明了,我仿造demo1,利用枚舉創建了一個同時管理push和pop的管理者,然后動畫的邏輯代碼集中在doPushAnimation
和doPopAnimation
中,很多內容都在demo1中說明了,下面的注釋就比較簡單了,來看看
//Push動畫邏輯
- (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
XWMagicMoveController *fromVC = (XWMagicMoveController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
XWMagicMovePushController *toVC = (XWMagicMovePushController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//拿到當前點擊的cell的imageView
XWMagicMoveCell *cell = (XWMagicMoveCell *)[fromVC.collectionView cellForItemAtIndexPath:fromVC.currentIndexPath];
UIView *containerView = [transitionContext containerView];
//snapshotViewAfterScreenUpdates 對cell的imageView截圖保存成另一個視圖用于過渡,并將視圖轉換到當前控制器的坐標
UIView *tempView = [cell.imageView snapshotViewAfterScreenUpdates:NO];
tempView.frame = [cell.imageView convertRect:cell.imageView.bounds toView: containerView];
//設置動畫前的各個控件的狀態
cell.imageView.hidden = YES;
toVC.view.alpha = 0;
toVC.imageView.hidden = YES;
//tempView 添加到containerView中,要保證在最前方,所以后添加
[containerView addSubview:toVC.view];
[containerView addSubview:tempView];
//開始做動畫
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.55 initialSpringVelocity:1 / 0.55 options:0 animations:^{
tempView.frame = [toVC.imageView convertRect:toVC.imageView.bounds toView:containerView];
toVC.view.alpha = 1;
} completion:^(BOOL finished) {
//tempView先隱藏不銷毀,pop的時候還會用
tempView.hidden = YES;
toVC.imageView.hidden = NO;
//如果動畫過渡取消了就標記不完成,否則才完成,這里可以直接寫YES,如果有手勢過渡才需要判斷,必須標記,否則系統不會中動畫完成的部署,會出現無法交互之類的bug
[transitionContext completeTransition:YES];
}];
}
//Pop動畫邏輯
- (void)doPopAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
XWMagicMovePushController *fromVC = (XWMagicMovePushController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
XWMagicMoveController *toVC = (XWMagicMoveController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
XWMagicMoveCell *cell = (XWMagicMoveCell *)[toVC.collectionView cellForItemAtIndexPath:toVC.currentIndexPath];
UIView *containerView = [transitionContext containerView];
//這里的lastView就是push時候初始化的那個tempView
UIView *tempView = containerView.subviews.lastObject;
//設置初始狀態
cell.imageView.hidden = YES;
fromVC.imageView.hidden = YES;
tempView.hidden = NO;
[containerView insertSubview:toVC.view atIndex:0];
[UIView animateWithDuration:[self transitionDuration:transitionContext] delay:0.0 usingSpringWithDamping:0.55 initialSpringVelocity:1 / 0.55 options:0 animations:^{
tempView.frame = [cell.imageView convertRect:cell.imageView.bounds toView:containerView];
fromVC.view.alpha = 0;
} completion:^(BOOL finished) {
//由于加入了手勢必須判斷
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
if ([transitionContext transitionWasCancelled]) {//手勢取消了,原來隱藏的imageView要顯示出來
//失敗了隱藏tempView,顯示fromVC.imageView
tempView.hidden = YES;
fromVC.imageView.hidden = NO;
}else{//手勢成功,cell的imageView也要顯示出來
//成功了移除tempView,下一次pop的時候又要創建,然后顯示cell的imageView
cell.imageView.hidden = NO;
[tempView removeFromSuperview];
}
}];
}
2、 然后將這個動畫過渡管理者和demo1中創建的手勢過渡管理者分別放到正確的代理方法中,用起來就可以了
DEMO THREE
1、 直接看看doPushAnimation
和doPopAnimation
的動畫邏輯,這次使用了CAGradientLayer給動畫的過程增加了陰影
//Push動畫邏輯
- (void)doPushAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//還是使用截圖大法來完成動畫,不然還是會有奇妙的bug;
UIView *tempView = [fromVC.view snapshotViewAfterScreenUpdates:NO];
tempView.frame = fromVC.view.frame;
UIView *containerView = [transitionContext containerView];
//將將要動畫的視圖加入containerView
[containerView addSubview:toVC.view];
[containerView addSubview:tempView];
fromVC.view.hidden = YES;
[containerView insertSubview:toVC.view atIndex:0];
//設置AnchorPoint,并增加3D透視效果
[tempView setAnchorPointTo:CGPointMake(0, 0.5)];
CATransform3D transfrom3d = CATransform3DIdentity;
transfrom3d.m34 = -0.002;
containerView.layer.sublayerTransform = transfrom3d;
//增加陰影
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;
[tempView 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:^{
//翻轉截圖視圖
tempView.layer.transform = CATransform3DMakeRotation(-M_PI_2, 0, 1, 0);
//給陰影效果動畫
fromShadow.alpha = 1.0;
toShadow.alpha = 0.0;
} completion:^(BOOL finished) {
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
if ([transitionContext transitionWasCancelled]) {
//失敗后記得移除截圖,下次push又會創建
[tempView removeFromSuperview];
fromVC.view.hidden = NO;
}
}];
}}
//Pop動畫邏輯
- (void)doPopAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
UIView *containerView = [transitionContext containerView];
//拿到push時候的的截圖視圖
UIView *tempView = containerView.subviews.lastObject;
[containerView addSubview:toVC.view];
[UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
//把截圖視圖翻轉回來
tempView.layer.transform = CATransform3DIdentity;
fromVC.view.subviews.lastObject.alpha = 1.0;
tempView.subviews.lastObject.alpha = 0.0;
} completion:^(BOOL finished) {
if ([transitionContext transitionWasCancelled]) {
[transitionContext completeTransition:NO];
}else{
[transitionContext completeTransition:YES];
[tempView removeFromSuperview];
toVC.view.hidden = NO;
}
}];}
2、 最后用上去在加上手勢就是這個樣子啦
DEMO FOUR
1、 直接看看doPresentAnimation
和doDismissAnimation
的動畫邏輯,這次使用了CASharpLayer和UIBezierPath
//Present動畫邏輯
- (void)presentAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *toVC = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
//拿到控制器獲取button的frame來設置動畫的開始結束的路徑
UINavigationController *fromVC = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
XWCircleSpreadController *temp = fromVC.viewControllers.lastObject;
UIView *containerView = [transitionContext containerView];
[containerView addSubview:toVC.view];
//畫兩個圓路徑
UIBezierPath *startCycle = [UIBezierPath bezierPathWithOvalInRect:temp.buttonFrame];
//通過如下方法計算獲取在x和y方向按鈕距離邊緣的最大值,然后利用勾股定理即可算出最大半徑
CGFloat x = MAX(temp.buttonFrame.origin.x, containerView.frame.size.width - temp.buttonFrame.origin.x);
CGFloat y = MAX(temp.buttonFrame.origin.y, containerView.frame.size.height - temp.buttonFrame.origin.y);
//勾股定理計算半徑
CGFloat radius = sqrtf(pow(x, 2) + pow(y, 2));
//以按鈕中心為圓心,按鈕中心到屏幕邊緣的最大距離為半徑,得到轉場后的path
UIBezierPath *endCycle = [UIBezierPath bezierPathWithArcCenter:containerView.center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
//創建CAShapeLayer進行遮蓋
CAShapeLayer *maskLayer = [CAShapeLayer layer];
//設置layer的path保證動畫后layer不會回彈
maskLayer.path = endCycle.CGPath;
//將maskLayer作為toVC.View的遮蓋
toVC.view.layer.mask = maskLayer;
//創建路徑動畫
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.delegate = self;
//動畫是加到layer上的,所以必須為CGPath,再將CGPath橋接為OC對象
maskLayerAnimation.fromValue = (__bridge id)(startCycle.CGPath);
maskLayerAnimation.toValue = (__bridge id)((endCycle.CGPath));
maskLayerAnimation.duration = [self transitionDuration:transitionContext];
maskLayerAnimation.delegate = self;
//設置淡入淡出
maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[maskLayerAnimation setValue:transitionContext forKey:@"transitionContext"];
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
}
//Dismiss動畫邏輯
- (void)dismissAnimation:(id<UIViewControllerContextTransitioning>)transitionContext{
UIViewController *fromVC = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
UINavigationController *toVC = (UINavigationController *)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
XWCircleSpreadController *temp = toVC.viewControllers.lastObject;
UIView *containerView = [transitionContext containerView];
//畫兩個圓路徑
CGFloat radius = sqrtf(containerView.frame.size.height * containerView.frame.size.height + containerView.frame.size.width * containerView.frame.size.width) / 2;
UIBezierPath *startCycle = [UIBezierPath bezierPathWithArcCenter:containerView.center radius:radius startAngle:0 endAngle:M_PI * 2 clockwise:YES];
UIBezierPath *endCycle = [UIBezierPath bezierPathWithOvalInRect:temp.buttonFrame];
//創建CAShapeLayer進行遮蓋
CAShapeLayer *maskLayer = [CAShapeLayer layer];
maskLayer.fillColor = [UIColor greenColor].CGColor;
maskLayer.path = endCycle.CGPath;
fromVC.view.layer.mask = maskLayer;
//創建路徑動畫
CABasicAnimation *maskLayerAnimation = [CABasicAnimation animationWithKeyPath:@"path"];
maskLayerAnimation.delegate = self;
maskLayerAnimation.fromValue = (__bridge id)(startCycle.CGPath);
maskLayerAnimation.toValue = (__bridge id)((endCycle.CGPath));
maskLayerAnimation.duration = [self transitionDuration:transitionContext];
maskLayerAnimation.delegate = self;
maskLayerAnimation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
[maskLayerAnimation setValue:transitionContext forKey:@"transitionContext"];
[maskLayer addAnimation:maskLayerAnimation forKey:@"path"];
}
2、最后在animationDidStop的代理方法中處理到動畫的完成邏輯,處理方式都類似
- (void)animationDidStop:(CAAnimation *)anim finished:(BOOL)flag{
switch (_type) {
case XWCircleSpreadTransitionTypePresent:{
id<UIViewControllerContextTransitioning> transitionContext = [anim valueForKey:@"transitionContext"];
[transitionContext completeTransition:YES];
[transitionContext viewControllerForKey:UITransitionContextToViewKey].view.layer.mask = nil;
}
break;
case XWCircleSpreadTransitionTypeDismiss:{
id<UIViewControllerContextTransitioning> transitionContext = [anim valueForKey:@"transitionContext"];
[transitionContext completeTransition:![transitionContext transitionWasCancelled]];
if ([transitionContext transitionWasCancelled]) {
[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey].view.layer.mask = nil;
}
}
break;
}
}
3、 最后用上去在加上手勢就是這個樣子啦
總結
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