iOS自定義轉(zhuǎn)場動畫-push和pop

iOS7.0后蘋果提供了自定義轉(zhuǎn)場動畫的API溢吻,利用這些API我們可以改變 push和pop(navigation非模態(tài)),present和dismiss(模態(tài)),標(biāo)簽切換(tabbar)的默認(rèn)轉(zhuǎn)場動畫趟佃。

主要涉及的API

1耸袜、UIViewControllerAnimatedTransitioning:轉(zhuǎn)場動畫協(xié)議友多,實現(xiàn)此協(xié)議定義轉(zhuǎn)場的動畫行為。

// 定義轉(zhuǎn)場動畫的時間
- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;

// 定義轉(zhuǎn)場動畫的行為
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;

2堤框、 UIViewControllerContextTransitioning:轉(zhuǎn)場動畫上下文域滥,這個協(xié)議定義了轉(zhuǎn)場動畫具體參數(shù),控制轉(zhuǎn)場動畫的狀態(tài)蜈抓,這個協(xié)議一般由系統(tǒng)實現(xiàn)启绰,在轉(zhuǎn)場發(fā)生時提供給我們使用。

FromTo:轉(zhuǎn)場是兩個視圖控制器(ViewController)的行為沟使,由一個視圖控制器切換到另一個視圖控制器委可,原先呈現(xiàn)的視圖控制器叫FromViewController,將要呈現(xiàn)的視圖控制器叫ToViewController腊嗡,那么FromViewController的view叫做FromView撤缴,ToViewController的view叫做ToView

對應(yīng)push和pop來說是兩個不同的轉(zhuǎn)場叽唱,它們的From和To在兩個轉(zhuǎn)場中使相互調(diào)換的屈呕。


控制器A push到 控制器B,那么From是A棺亭, To是B
控制器B pop到 控制器A虎眨, 那么From是B, To是A

containerView:轉(zhuǎn)場動畫完成都是在containerView里面镶摘。

3嗽桩、UIViewControllerInteractiveTransitioning:轉(zhuǎn)場的交互協(xié)議,用來控制轉(zhuǎn)場動畫的狀態(tài)或進(jìn)度凄敢。

//設(shè)置轉(zhuǎn)場進(jìn)度, 取值范圍 [0..1]
- (void)updateInteractiveTransition:(CGFloat)percentComplete;
//完成轉(zhuǎn)場碌冶,呈現(xiàn)to
- (void)finishInteractiveTransition;
//取消轉(zhuǎn)場,呈現(xiàn)from
- (void)cancelInteractiveTransition;

4涝缝、UIPercentDrivenInteractiveTransition:官方提供的實現(xiàn)UIViewControllerInteractiveTransitioning協(xié)議的類扑庞,可以直接使用譬重。

上面簡單的介紹了轉(zhuǎn)場動畫涉及的API,這一節(jié)主要通過導(dǎo)航控制器的push和pop轉(zhuǎn)場動畫來介紹這些自定義轉(zhuǎn)場動畫的流程罐氨。

push轉(zhuǎn)場動畫

1臀规、準(zhǔn)備工作:
帶有導(dǎo)航控制器的ViewController類,要push到的下一級控制器SecondViewController類栅隐。
2塔嬉、在類HSPushAnimation中實現(xiàn)UIViewControllerAnimatedTransitioning協(xié)議,定義push轉(zhuǎn)場動畫行為租悄。

@interface HSPushAnimation : NSObject <UIViewControllerAnimatedTransitioning>
@end
@implementation HSPushAnimation
//設(shè)置轉(zhuǎn)場動畫的時長
- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext{
    return 2.f;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
    //from
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
   //to
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    UIView* toView = nil;
    UIView* fromView = nil;
    //UITransitionContextFromViewKey和UITransitionContextToViewKey定義在iOS8.0以后的SDK中谨究,所以在iOS8.0以下SDK中將toViewController和fromViewController的view設(shè)置給toView和fromView
    //iOS8.0 之前和之后view的層次結(jié)構(gòu)發(fā)生變化,所以iOS8.0以后UITransitionContextFromViewKey獲得view并不是fromViewController的view
    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
        fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    } else {
        fromView = fromViewController.view;
        toView = toViewController.view;
    }
    //這個非常重要泣棋,將toView加入到containerView中
    [[transitionContext containerView]  addSubview:toView];
    
    CGFloat width = [UIScreen mainScreen].bounds.size.width;
    CGFloat height = [UIScreen mainScreen].bounds.size.height;
    
    toView.frame = CGRectMake(width, 0, width, height);
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        toView.frame = CGRectMake(0, 0, width, height);
    } completion:^(BOOL finished) {
        [transitionContext completeTransition:YES];
    }];

}
@end

上面代碼定義了一個非常簡單的動畫记盒,toView從左到右覆蓋fromView,和系統(tǒng)默認(rèn)動畫一樣外傅,只是時間設(shè)置的比較長纪吮。

轉(zhuǎn)場動畫所有要呈現(xiàn)的元素都要放在containerView中,fromView默認(rèn)已經(jīng)在containerView中了萎胰。

3碾盟、指定push要使用的轉(zhuǎn)場動畫行為:由于要自定義轉(zhuǎn)場動畫所以我們需要指定轉(zhuǎn)場動畫行為。push轉(zhuǎn)場的動畫行為是由UINavigationControllerDelegate協(xié)議指定技竟,所以我們在ViewController設(shè)置導(dǎo)航控制器的Delegate:

- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    self.navigationController.delegate = self;
}

實現(xiàn)以下協(xié)議冰肴,指定動畫類:

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    if (operation == UINavigationControllerOperationPush) {
        return [[HSPushAnimation alloc] init];
    }
    return nil;
}

這個方法可以分別指定push和pop的動畫類,這里我們只定義push動畫榔组,所以只要指定UINavigationControllerOperationPush時的動畫行為即可熙尉。

這樣push轉(zhuǎn)場動畫就完成了,效果圖如下:


自定義push動畫

pop轉(zhuǎn)場動畫

1搓扯、pop轉(zhuǎn)場動畫和push轉(zhuǎn)場動畫類似检痰,在類HSPopAnimation中實現(xiàn)UIViewControllerAnimatedTransitioning協(xié)議。

@implementation HSPopAnimation

- (NSTimeInterval)transitionDuration:(id <UIViewControllerContextTransitioning>)transitionContext{
    return 0.5;
}

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext{
    
    UIViewController *toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    
    UIView* toView = nil;
    UIView* fromView = nil;
   
    if ([transitionContext respondsToSelector:@selector(viewForKey:)]) {
        fromView = [transitionContext viewForKey:UITransitionContextFromViewKey];
        toView = [transitionContext viewForKey:UITransitionContextToViewKey];
    } else {
        fromView = fromViewController.view;
        toView = toViewController.view;
    }
    
    //將toView加到fromView的下面锨推,非常重要G摺!换可!
    [[transitionContext containerView] insertSubview:toView belowSubview:fromView];
    
    CGFloat width = [UIScreen mainScreen].bounds.size.width;
    CGFloat height = [UIScreen mainScreen].bounds.size.height;
    
    fromView.frame = CGRectMake(0, 0, width, height);
    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromView.frame = CGRectMake(width, 0, width, height);
    } completion:^(BOOL finished) {
        
        [transitionContext completeTransition:!transitionContext.transitionWasCancelled];
    }];
}

@end

這里pop動畫基本和push動畫是相反的過程椎椰,當(dāng)然你也可以指定別的方式的動畫。
這里from沾鳄、to和push動畫里面的from慨飘、to值已經(jīng)互換了,所以如果將push和pop動畫寫在一起的話译荞,要特別注意瓤的,不過建議將push和pop動畫分別定義到不同的類中休弃,方便管理。
2堤瘤、在SecondViewControlle類中設(shè)置導(dǎo)航控制器的Delegate玫芦,并實現(xiàn)以下協(xié)議:

- (id<UIViewControllerAnimatedTransitioning>)navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC{
    if (operation == UINavigationControllerOperationPop) {
        return [[HSPopAnimation alloc] init];
    }
    return nil;
}

pop轉(zhuǎn)場動畫就完成了浆熔,效果圖如下:


pop轉(zhuǎn)場動畫

可交互轉(zhuǎn)場動畫

先來看看可交互轉(zhuǎn)場動畫效果


可交互轉(zhuǎn)場動畫

可交互轉(zhuǎn)場動畫的實現(xiàn)需要實現(xiàn)UIViewControllerInteractiveTransitioning協(xié)議本辐,幸好官方給我們提供了UIPercentDrivenInteractiveTransition類可以直接使用,你也可以繼承UIPercentDrivenInteractiveTransition來使用医增。
UIViewControllerInteractiveTransitioning協(xié)議的功能主要是控制轉(zhuǎn)場動畫的狀態(tài)慎皱,即動畫完成的百分比,所以只有在轉(zhuǎn)場中才有用叶骨。

比如我們通過[self.navigationController popViewControllerAnimated:YES]觸發(fā)pop轉(zhuǎn)場動畫茫多,然后在轉(zhuǎn)場動畫結(jié)束之前通過- (void)updateInteractiveTransition:(CGFloat)percentComplete更改轉(zhuǎn)場動畫的完成的百分比,那么轉(zhuǎn)場動畫將由實現(xiàn)UIViewControllerInteractiveTransitioning的類接管忽刽,而不是由定時器管理天揖,之后就可以隨意設(shè)置動畫狀態(tài)了。

交互動畫往往配合手勢操作跪帝,手勢操作產(chǎn)生一序列百分比數(shù)通過updateInteractiveTransition方法實時更新轉(zhuǎn)場動畫狀態(tài)今膊。

1、現(xiàn)在添加為SecondViewControlle的view添加手勢:

//添加pan手勢
UIPanGestureRecognizer * pan = [[UIPanGestureRecognizer alloc] init];
[pan addTarget:self action:@selector(panGestureRecognizerAction:)];
[self.view addGestureRecognizer:pan];

2伞剑、觸發(fā)轉(zhuǎn)場動畫斑唬,通過手勢產(chǎn)生百分比數(shù)值,更新轉(zhuǎn)場動畫狀態(tài):

- (void)panGestureRecognizerAction:(UIPanGestureRecognizer *)pan{
    
    //產(chǎn)生百分比
    CGFloat process = [pan translationInView:self.view].x / ([UIScreen mainScreen].bounds.size.width);
    
    process = MIN(1.0,(MAX(0.0, process)));
    
    if (pan.state == UIGestureRecognizerStateBegan) {
        self.interactiveTransition = [UIPercentDrivenInteractiveTransition new];
        //觸發(fā)pop轉(zhuǎn)場動畫
        [self.navigationController popViewControllerAnimated:YES];
        
    }else if (pan.state == UIGestureRecognizerStateChanged){
        [self.interactiveTransition updateInteractiveTransition:process];
    }else if (pan.state == UIGestureRecognizerStateEnded
              || pan.state == UIGestureRecognizerStateCancelled){
        if (process > 0.5) {
            [ self.interactiveTransition finishInteractiveTransition];
        }else{
            [ self.interactiveTransition cancelInteractiveTransition];
        }
        self.interactiveTransition = nil;
    }
}

手勢開始狀態(tài):手勢開始時創(chuàng)建UIPercentDrivenInteractiveTransition對象黎泣,通過popViewControllerAnimated方法觸發(fā)轉(zhuǎn)場動畫恕刘。
手勢變化狀態(tài):通過計算得到的百分比實時更新轉(zhuǎn)場動畫的狀態(tài)。
手勢取消或者結(jié)束狀態(tài):根據(jù)完成的百分比決定是否完成轉(zhuǎn)場或者取消轉(zhuǎn)場抒倚。

3褐着、開始轉(zhuǎn)場動畫時,就需要指定一個實現(xiàn)UIViewControllerInteractiveTransitioning協(xié)議的對象來控制轉(zhuǎn)場動畫的狀態(tài)托呕,否則轉(zhuǎn)場動畫狀態(tài)由定時器管理献起。在SecondViewControlle類中,我們通過UINavigationControllerDelegate協(xié)議將interactiveTransition對象傳給UIKit:

- (void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    self.navigationController.delegate = self;
}
- (id<UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController *)navigationController interactionControllerForAnimationController:(id<UIViewControllerAnimatedTransitioning>)animationController{
    if ([animationController isKindOfClass:[HSPopAnimation class]]) {
        return self.interactiveTransition;
    }
    return nil;
}

以上步驟就將pop的可交互轉(zhuǎn)場動畫完成了镣陕。

push和pop轉(zhuǎn)場動畫基本流程

push和pop轉(zhuǎn)場動畫基本流程圖

Note:

  • 動畫的狀態(tài)和轉(zhuǎn)場的狀態(tài)是不一樣的谴餐,動畫完成后,不代表轉(zhuǎn)場完成呆抑,所以我們要在動畫的completion里面決定是否完成轉(zhuǎn)場:[transitionContext completeTransition:!transitionContext.transitionWasCancelled];
  • 轉(zhuǎn)場是一個過程岂嗓,所有的動畫都在containerView里面完成。
  • 不需要交互的轉(zhuǎn)場interactionControllerForAnimationController方法一定要返回nil

?文章的源碼將在完成完成下篇present和dismiss動畫時候上傳鹊碍。

本文作為讀書筆記厌殉,不是科普讀物食绿,所以知識有可能理解錯誤,如有請您不吝賜教公罕。

Demo地址:https://github.com/cnthinkcode/HSPresentTransitionDemo

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末器紧,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子楼眷,更是在濱河造成了極大的恐慌铲汪,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件罐柳,死亡現(xiàn)場離奇詭異掌腰,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)张吉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門齿梁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肮蛹,你說我怎么就攤上這事勺择。” “怎么了伦忠?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵省核,是天一觀的道長。 經(jīng)常有香客問我缓苛,道長芳撒,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任未桥,我火速辦了婚禮笔刹,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘冬耿。我一直安慰自己舌菜,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布亦镶。 她就那樣靜靜地躺著日月,像睡著了一般。 火紅的嫁衣襯著肌膚如雪缤骨。 梳的紋絲不亂的頭發(fā)上爱咬,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天,我揣著相機(jī)與錄音绊起,去河邊找鬼精拟。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蜂绎。 我是一名探鬼主播栅表,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼师枣!你這毒婦竟也來了怪瓶?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤践美,失蹤者是張志新(化名)和其女友劉穎洗贰,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體拨脉,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡哆姻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年宣增,在試婚紗的時候發(fā)現(xiàn)自己被綠了玫膀。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡爹脾,死狀恐怖帖旨,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情灵妨,我是刑警寧澤解阅,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站泌霍,受9級特大地震影響货抄,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜朱转,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一蟹地、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧藤为,春花似錦怪与、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至存淫,卻和暖如春耘斩,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背桅咆。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工括授, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓刽脖,卻偏偏與公主長得像羞海,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子曲管,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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