ViewController自定義轉(zhuǎn)場(chǎng)-基礎(chǔ)

從iOS7開始颜曾,蘋果更新了自定義ViewController轉(zhuǎn)場(chǎng)的API褐健,這些新增的類和接口讓很多人困惑,望而卻步物蝙。本文就從這些API入口炎滞,讓讀者理清這些API錯(cuò)綜復(fù)雜的關(guān)系。

幾個(gè)protocol

講自定義轉(zhuǎn)場(chǎng)就離不開這幾個(gè)protocol:

  • UIViewControllerContextTransitioning
  • UIViewControllerAnimatedTransitioning
  • UIViewControllerInteractiveTransitioning
  • UIViewControllerTransitioningDelegate
  • UINavigationControllerDelegate
  • UITabBarControllerDelegate

乍一看很多诬乞,其實(shí)很簡(jiǎn)單册赛,我們可以將其分為三類:

  1. 描述ViewController轉(zhuǎn)場(chǎng)的:
    UIViewControllerTransitioningDelegate,UINavigationControllerDelegate,UITabBarControllerDelegate
  2. 定義動(dòng)畫內(nèi)容的
    UIViewControllerAnimatedTransitioning,UIViewControllerInteractiveTransitioning
  3. 表示動(dòng)畫上下文的
    UIViewControllerContextTransitioning

描述ViewController轉(zhuǎn)場(chǎng)的

細(xì)說之前先扯個(gè)蛋:

為什么蘋果要引入這一套API?因?yàn)樵趇OS7之前震嫉,做轉(zhuǎn)場(chǎng)動(dòng)畫很麻煩森瘪,要寫一大堆代碼在ViewController中。引入這一套API之后票堵,在豐富功能的同時(shí)極大程度地降低了代碼耦合扼睬,實(shí)現(xiàn)方式就是將之前在ViewController里面的代碼通過protocol分離了出來。

順著這個(gè)思路往下想悴势,實(shí)現(xiàn)自定義轉(zhuǎn)場(chǎng)動(dòng)畫首先需要找到ViewController的delegate窗宇。蘋果告訴我們切換ViewController有三種形式:UITabBarController內(nèi)部切換,UINavigationController切換特纤,present modal ViewController军俊。這三種方式是不是需要不同的protocol呢?

我們分別來看下:

  • UIViewControllerTransitioningDelegate 自定義模態(tài)轉(zhuǎn)場(chǎng)動(dòng)畫時(shí)使用捧存。
    設(shè)置UIViewController的屬性transitioningDelegate粪躬。
    @property (nullable, nonatomic, weak) id <UIViewControllerTransitioningDelegate> transitioningDelegate
  • UINavigationControllerDelegate 自定義navigation轉(zhuǎn)場(chǎng)動(dòng)畫時(shí)使用。
    設(shè)置UINavigationController的屬性delegate
    @property(nullable, nonatomic, weak) id<UINavigationControllerDelegate> delegate
  • UITabBarControllerDelegate 自定義tab轉(zhuǎn)場(chǎng)動(dòng)畫時(shí)使用昔穴。
    設(shè)置UITabBarController的屬性delegate
    @property(nullable, nonatomic,weak) id<UITabBarControllerDelegate> delegate

實(shí)際上這三個(gè)protocol干的事情是一樣的短蜕,就是我們“扯淡”的內(nèi)容,只不過他們的應(yīng)用場(chǎng)景不同罷了傻咖。我們下面以UINavigationControllerDelegate為例朋魔,其他的類似。

定義動(dòng)畫內(nèi)容的

UINavigationControllerDelegate主要包含這兩個(gè)方法:

- (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);

兩個(gè)方法分別返回UIViewControllerInteractiveTransitioningUIViewControllerAnimatedTransitioning卿操,它們的任務(wù)是描述動(dòng)畫行為(轉(zhuǎn)場(chǎng)動(dòng)畫如何執(zhí)行警检,就看它倆的)。
從名字可以看出害淤,這兩個(gè)protocol的區(qū)別在于是否是interactive的扇雕。如何理解?****interactive動(dòng)畫可以根據(jù)輸入信息的變化改變動(dòng)畫的進(jìn)程窥摄。****例如iOS系統(tǒng)為UINavigationController提供的默認(rèn)右滑退出手勢(shì)就是一個(gè)interactive 動(dòng)畫镶奉,整個(gè)動(dòng)畫的進(jìn)程由用戶手指的移動(dòng)距離控制。

我們來看下相對(duì)簡(jiǎn)單的UIViewControllerAnimatedTransitioning:

- (NSTimeInterval)transitionDuration:(nullable id <UIViewControllerContextTransitioning>)transitionContext;
- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext;
@optional
- (void)animationEnded:(BOOL) transitionCompleted;

transitionDuration返回動(dòng)畫的執(zhí)行時(shí)間,animateTransition處理具體的動(dòng)畫哨苛,animationEnded是optional鸽凶,大部分情況下不需要處理。
這里出現(xiàn)了我們要講的最后一個(gè)protocol:UIViewControllerContextTransitioning建峭。

表示動(dòng)畫上下文的

UIViewControllerContextTransitioning也是唯一一個(gè)不需要我們實(shí)現(xiàn)的protocol玻侥。

Do not adopt this protocol in your own classes, nor should you directly create objects that adopt this protocol.

UIViewControllerContextTransitioning提供了一系列方法,為interactive和非interactive動(dòng)畫提供上下文:

//轉(zhuǎn)場(chǎng)動(dòng)畫發(fā)生在該View中
- (nullable UIView *)containerView;
//上報(bào)動(dòng)畫執(zhí)行完畢
- (void)completeTransition:(BOOL)didComplete;
//根據(jù)key返回一個(gè)ViewController亿蒸。我們通過UITransitionContextFromViewControllerKey找到將被替換掉的ViewController凑兰,通過UITransitionContextToViewControllerKey找到將要顯示的ViewController
- (nullable __kindof UIViewController *)viewControllerForKey:(NSString *)key;

還有一些其他的方法,我們以后用到再說边锁。

下面我們通過一個(gè)簡(jiǎn)單的Demo串聯(lián)理解下姑食。

DEMO

transitionDemo.gif

這是一個(gè)縮放同時(shí)修改透明度的動(dòng)畫,我們來看下如何實(shí)現(xiàn)茅坛。
在上面的講解中矢门,我們通過倒推的方式來理解轉(zhuǎn)場(chǎng)動(dòng)畫中用到的protocol,在Demo 中灰蛙,我們會(huì)從創(chuàng)建動(dòng)畫開始。

第一步:創(chuàng)建動(dòng)畫

由上面的解析得知隔躲,動(dòng)畫是在UIViewControllerAnimatedTransitioning中定義的摩梧,所以我們首先創(chuàng)建實(shí)現(xiàn)UIViewControllerAnimatedTransitioning的對(duì)象:JLScaleTransition

JLScaleTransition.h

@interface JLScaleTransition : NSObject<UIViewControllerAnimatedTransitioning>
@end

JLScaleTransition.m

@implementation JLScaleTransition

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

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *toVC = (UIViewController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromVC = (UIViewController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIView * containerView = [transitionContext containerView];
    UIView * fromView = fromVC.view;
    UIView * toView = toVC.view;
    
    [containerView addSubview:toView];
    
    [[transitionContext containerView] bringSubviewToFront:fromView];
    
    NSTimeInterval duration = [self transitionDuration:transitionContext];
    [UIView animateWithDuration:duration animations:^{
        fromView.alpha = 0.0;
        fromView.transform = CGAffineTransformMakeScale(0.2, 0.2);
        toView.alpha = 1.0;
    } completion:^(BOOL finished) {
        fromView.transform = CGAffineTransformMakeScale(1, 1);
        [transitionContext completeTransition:YES];
    }];
}

animateTransition中宣旱,我們分別獲取兩個(gè)ViewController的view仅父,將toView添加到containerView中,然后執(zhí)行動(dòng)畫浑吟。為了理解containerViewfromView,toView的關(guān)系笙纤,我們添加幾個(gè)log來分析一下:

- (void)animateTransition:(id <UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController *toVC = (UIViewController*)[transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController *fromVC = (UIViewController*)[transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    UIView * containerView = [transitionContext containerView];
    UIView * fromView = fromVC.view;
    UIView * toView = toVC.view;
    NSLog(@"startAnimation! fromView = %@", fromView);
    NSLog(@"startAnimation! toView = %@", toView);
    for(UIView * view in containerView.subviews){
        NSLog(@"startAnimation! list container subviews: %@", view);
    }
    
    [containerView addSubview:toView];
    
    [[transitionContext containerView] bringSubviewToFront:fromView];
    
    NSTimeInterval duration = [self transitionDuration:transitionContext];
    [UIView animateWithDuration:duration animations:^{
        fromView.alpha = 0.0;
        fromView.transform = CGAffineTransformMakeScale(0.2, 0.2);
        toView.alpha = 1.0;
    } completion:^(BOOL finished) {
        fromView.transform = CGAffineTransformMakeScale(1, 1);
        [transitionContext completeTransition:YES];
        for(UIView * view in containerView.subviews){
            NSLog(@"endAnimation! list container subviews: %@", view);
        }
    }];
}

運(yùn)行l(wèi)og如下:

2016-06-29 13:50:48.512 JLTransition[1970:177922] startAnimation! fromView = <UIView: 0x7aaef4e0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7aaef5a0>>
2016-06-29 13:50:48.513 JLTransition[1970:177922] startAnimation! toView = <UIView: 0x796ac5a0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x796ac050>>
2016-06-29 13:50:48.513 JLTransition[1970:177922] startAnimation! list container subviews: <UIView: 0x7aaef4e0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x7aaef5a0>>
2016-06-29 13:50:49.017 JLTransition[1970:177922] endAnimation! list container subviews: <UIView: 0x796ac5a0; frame = (0 0; 320 568); autoresize = W+H; layer = <CALayer: 0x796ac050>>

可見,轉(zhuǎn)場(chǎng)執(zhí)行的時(shí)候组力,containerView中只包含fromView省容,轉(zhuǎn)場(chǎng)動(dòng)畫執(zhí)行完畢之后,containerView會(huì)將fromView移除燎字。因?yàn)?code>containerView不負(fù)責(zé)toView的添加腥椒,所以我們需要主動(dòng)將toView添加到containerView中。

注意候衍!非interactive轉(zhuǎn)場(chǎng)中笼蛛,動(dòng)畫結(jié)束之后需要執(zhí)行[transitionContext completeTransition:YES];(如果動(dòng)畫被取消,傳NO)蛉鹿;但是在interactive轉(zhuǎn)場(chǎng)中滨砍,動(dòng)畫是否結(jié)束是由外界控制的(用戶行為或者特定函數(shù)),需要在外部調(diào)用。

第二步:定義轉(zhuǎn)場(chǎng)

在第二部惋戏,我們需要實(shí)現(xiàn)UIViewControllerAnimatedTransitioning领追,并將第一步創(chuàng)建的JLScaleTransition對(duì)象返回。

JLScaleNavControlDelegate.h

@interface JLScaleNavControlDelegate : NSObject<UINavigationControllerDelegate>
@end

JLScaleNavControlDelegate.m

@implementation JLScaleNavControlDelegate
- (nullable id <UIViewControllerAnimatedTransitioning>) navigationController:(UINavigationController *)navigationController animationControllerForOperation:(UINavigationControllerOperation)operation fromViewController:(UIViewController *)fromVC toViewController:(UIViewController *)toVC
{
    return [JLScaleTransition new];
}
@end

這一步很簡(jiǎn)單日川,實(shí)現(xiàn)UIViewControllerAnimatedTransitioning對(duì)應(yīng)方法即可蔓腐。

第三步:設(shè)置轉(zhuǎn)場(chǎng)

設(shè)置轉(zhuǎn)場(chǎng)其實(shí)就是設(shè)置delegate(還記得我們“扯淡”的內(nèi)容吧)。

    self.navigationController.delegate = id<UINavigationControllerDelegate>
    self.transitioningDelegate = id<UIViewControllerTransitioningDelegate>
    self.tabBarController.delegate = id<UITabBarControllerDelegate>

設(shè)置delegate有兩種方式:通過代碼龄句;通過StoryBoard回论。

通過代碼設(shè)置

@interface ViewController ()
@property (nonatomic, strong) JLScaleNavControlDelegate * scaleNavDelegate;
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    self.scaleNavDelegate = [JLScaleNavControlDelegate new];
}

- (IBAction)triggerTransitionDelegate:(id)sender
{
    self.navigationController.delegate = self.scaleNavDelegate;
    [self.navigationController pushViewController:[TargetViewController new] animated:YES];
}

通過StoryBoard設(shè)置

在StoryBoard中為Navigation Bar添加一個(gè)Object,并且聲明為JLScaleNavControlDelegate(定義見上文)分歇。

storyboard添加delegate

按住control傀蓉,從navigation controller拖線置新添加的object,指定為delegate职抡。

storyboard設(shè)置delegate

NEXT

今天就到這里葬燎,源碼放在github,還包括一些復(fù)雜的動(dòng)畫缚甩,會(huì)持續(xù)更新谱净,后面有時(shí)間專門挑幾個(gè)效果牛逼的聊聊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末擅威,一起剝皮案震驚了整個(gè)濱河市壕探,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌郊丛,老刑警劉巖李请,帶你破解...
    沈念sama閱讀 207,248評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異厉熟,居然都是意外死亡导盅,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,681評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門揍瑟,熙熙樓的掌柜王于貴愁眉苦臉地迎上來白翻,“玉大人,你說我怎么就攤上這事绢片∴易郑” “怎么了?”我有些...
    開封第一講書人閱讀 153,443評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵杉畜,是天一觀的道長(zhǎng)纪蜒。 經(jīng)常有香客問我,道長(zhǎng)此叠,這世上最難降的妖魔是什么纯续? 我笑而不...
    開封第一講書人閱讀 55,475評(píng)論 1 279
  • 正文 為了忘掉前任随珠,我火速辦了婚禮,結(jié)果婚禮上猬错,老公的妹妹穿的比我還像新娘窗看。我一直安慰自己,他們只是感情好倦炒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,458評(píng)論 5 374
  • 文/花漫 我一把揭開白布显沈。 她就那樣靜靜地躺著,像睡著了一般逢唤。 火紅的嫁衣襯著肌膚如雪拉讯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,185評(píng)論 1 284
  • 那天鳖藕,我揣著相機(jī)與錄音魔慷,去河邊找鬼。 笑死著恩,一個(gè)胖子當(dāng)著我的面吹牛院尔,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播喉誊,決...
    沈念sama閱讀 38,451評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼邀摆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了伍茄?” 一聲冷哼從身側(cè)響起栋盹,我...
    開封第一講書人閱讀 37,112評(píng)論 0 261
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎幻林,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體音念,經(jīng)...
    沈念sama閱讀 43,609評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡沪饺,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,083評(píng)論 2 325
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了闷愤。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片整葡。...
    茶點(diǎn)故事閱讀 38,163評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖讥脐,靈堂內(nèi)的尸體忽然破棺而出遭居,到底是詐尸還是另有隱情,我是刑警寧澤旬渠,帶...
    沈念sama閱讀 33,803評(píng)論 4 323
  • 正文 年R本政府宣布俱萍,位于F島的核電站,受9級(jí)特大地震影響告丢,放射性物質(zhì)發(fā)生泄漏枪蘑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,357評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望岳颇。 院中可真熱鬧照捡,春花似錦、人聲如沸话侧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,357評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瞻鹏。三九已至悲立,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間乙漓,已是汗流浹背级历。 一陣腳步聲響...
    開封第一講書人閱讀 31,590評(píng)論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留叭披,地道東北人寥殖。 一個(gè)月前我還...
    沈念sama閱讀 45,636評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像涩蜘,于是被迫代替她去往敵國和親嚼贡。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,925評(píng)論 2 344

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