iOS中的跳轉(zhuǎn)動畫總結(jié)

不管任何App,對于跳轉(zhuǎn)這個最基礎(chǔ)的動畫都是必須的,而iOS的轉(zhuǎn)場動畫不僅只是動畫鸥昏,還涉及層級存和、跳轉(zhuǎn)限制、攜帶參數(shù)...等等一系列問題蜓席。所以捋清楚專場動畫的不同和使用方式顯得尤為重要,現(xiàn)在總結(jié)一下涉及該部分的技術(shù)參數(shù)內(nèi)容如下:

Modal presentation

  1. 設(shè)定ViewController的modalTransitionStyle屬性器一。
    這種方式也對應(yīng)Storyboard中對應(yīng)的segue transition的設(shè)置。這個屬性是一個枚舉類型厨内,其值代表已經(jīng)定制的幾種轉(zhuǎn)場風(fēng)格祈秕。這種方式也是最簡單的轉(zhuǎn)場渺贤,不帶有任何自定義的轉(zhuǎn)場效果代碼。
  2. 使用UIView的animation API實現(xiàn)自定義的動畫请毛。

這種方式是比較常見實現(xiàn)方式志鞍。除了官方的文檔以外,大量的Blog文章都會詳細講解這些API的用法方仿。UIView的animation API的使用比較直觀固棚,相對來說也是一種比較容易學(xué)習(xí)的動畫實現(xiàn)方式。

Navigation View Controller presentation

  1. 使用UIView的animation API兼丰。

與Modal presentation相同玻孟,盡管在Storyboard中有專門為Navigation View Controller定制的Push segue(iOS8中被Show segue取代,因為后者支持iOS8中引進的Adaptive AutoLayout)鳍征,但是Push segue并沒有transtion屬性黍翎,所以如果需要定制轉(zhuǎn)場效果,可以使用UIVIew艳丛,使用方法和Modal presentation相同匣掸。

  1. 使用CATransition類。

CATransition 看起來更像為Navigation View Controller和TabBar View Controller這樣的容器Controller定制的轉(zhuǎn)場效果類氮双。提供了很多內(nèi)置的的動畫效果碰酝。CATransition還可以結(jié)合 CoreImage的濾鏡CIFilter共同實現(xiàn)很炫的場景轉(zhuǎn)換。若想詳細了解CATransition的用法戴差,可以讀一讀蘋果的文檔送爸。

我 們注意到,轉(zhuǎn)場往往發(fā)生在流程切換的時候暖释。所以上面的轉(zhuǎn)場效果代碼袭厂,往往會放在自定義的轉(zhuǎn)場方法中(多見于使用Nib開發(fā))或放在自定義的 UIStoryboardSegue類中(多見于使用Storyboard開發(fā))。所以很多時候球匕,我們往往會碰到原生的轉(zhuǎn)場方法與定制的動畫效果有一定沖 突纹磺。因為像presentViewController:animated:completion:這類方法本身就自帶有內(nèi)置的動畫效果,自定義的動畫效 果往往在這個方法之外亮曹。所以很多時候需要用一些trick來避免這些問題橄杨。因此代碼的可讀性往往不會很好,并且寫的不好的時候還會帶來效率方面的問題照卦。

iOS7以后式矫,蘋果引進了新的Transition API。這些API的使用方式役耕,蘋果沒有給出一個官方的Guide衷佃,但是在網(wǎng)上,已經(jīng)有很多Blog和教程講解如何使用這些API蹄葱,比如這篇文章氏义。

新 的Transition API完全改變了上面提到的動畫與原生轉(zhuǎn)場接口不兼容的問題锄列。在新的API中,我們可以將動畫效果代碼單獨封裝到animator對象中惯悠,在設(shè)定好 View Controller的transitoningDelegate后邻邮,再調(diào)用原生的轉(zhuǎn)場方法,就會自動使用定制的動畫效果克婶⊥惭希考慮到現(xiàn)在大部分App已經(jīng)逐 漸放棄了對iOS6的支持,所以這種方法是目前推薦的轉(zhuǎn)場效果定制方法情萤。單獨封裝的動畫效果類在代碼管理上也更加方便鸭蛙。

這 里需要注意的一點,iOS6中引入的Storyboard Unwind Segue往往都需要一個Container View Controller筋岛。一個很常見的問題就是新手在定制Segue的時候往往會發(fā)生自定義的Unwind Segue不起作用娶视。這個問題一般都是由于沒有正確實現(xiàn)Container View Controller所需的方法帶來的。

Storyboard中的轉(zhuǎn)場

自 iOS5引入Storyboard之后睁宰,iOS開發(fā)者在除了原有的Nib開發(fā)的基礎(chǔ)上又有了新的方式來組織自己的UI和流程肪获。Storyboard相對于 傳統(tǒng)的Nib,能夠更加清晰的體現(xiàn)業(yè)務(wù)的流程柒傻,因此很受開發(fā)者歡迎孝赫。如今,很多教程都以Storyboard開發(fā)方式來講解红符。而Storyboard中的 Segue則是對轉(zhuǎn)場流程的進一步封裝青柄。這個概念在Storyboard中至關(guān)重要,也是實現(xiàn)自定義轉(zhuǎn)場的關(guān)鍵角色预侯。

自定義Segue

自定義Segue的方式很簡單刹前,只要創(chuàng)建一個UIStoryboardSegue子類,并實現(xiàn)其perform方法即可雌桑。一個簡單的實現(xiàn)如下:

//
//  CAlayer.m
//  CureFunNew
//
//  Created by Hubery on 2017/9/9.
//  Copyright ? 2017年 TLQ. All rights reserved.
//
- (void)perform
{
// Modal presentation segue
UIViewController *fromController = self.sourceViewController;
UIViewController *toController = self.destinationViewController;
[fromController presentViewController:toController animated:YES completion:^{
// Completion code here
}];
}
或者

//
//  CAlayer.m
//  CureFunNew
//
//  Created by Hubery on 2017/9/9.
//  Copyright ? 2017年 TLQ. All rights reserved.
//
- (void)perform
{
// Navigation ViewController segue(Push segue, Show segue in iOS8).
UIViewController *fromController = self.sourceViewController;
UIViewController *toController = self.destinationViewController;
[fromController.navigationController pushViewController:toController animated:YES];
}

自定義Unwind Segue

自 定義Unwind Segue的方式與上面幾乎完全一樣,只不過調(diào)用的接口由presentViewController:animated:completion:和 pushViewController:animated:換成dismissViewControllerAnimated:completion:和 popToViewController:animated:祖今。

但是Unwind Segue與普通的Segue有一個很大的不同校坑,就是Unwind Segue的調(diào)用通常是由一個Container View Controller完成的。在iOS SDK的UIKit框架中千诬,Navigation View Controller和TabBar View Controller都是常用的Container View Controller耍目。

那么為什么Unwind Segue需要一個Container View Controllerl的支持?

這 里就需要提一下Unwind Segue的設(shè)計初衷及其工作方式徐绑。之所以引入Unwind Segue邪驮,是為了應(yīng)付任意跳轉(zhuǎn)的情況,即從任意一個View Controller轉(zhuǎn)場到特定的View Controller傲茄。在Nib的時代毅访,這種工作往往通過delegate來完成沮榜。但是有了Unwind Segue以后,我們只要在需要跳轉(zhuǎn)到的這個特定的View Controller類中實現(xiàn)一個簽名為- (IBAction)unwindMethod:(UIStoryboardSegue *)segue這樣的方法即可(其中unwindMethod可以替換為任何你喜歡的名稱喻粹,但注意蟆融,當(dāng)存在多個這樣的方法時,名稱不要相同守呜,以免發(fā)生沖 突型酥,造成不可預(yù)料的后果)。這樣查乒,我們就可以在任意的View Controller(除了含有這個方法本身的View Controller)通過連接Segue來實現(xiàn)任意View Controller跳轉(zhuǎn)到當(dāng)前View Controller弥喉。不用再多寫一行代碼,這些都可以通過Interface Builder搞定玛迄,非常方便由境。

Unwind Segue的工作原理大致如下:

● 當(dāng)我們通過UI事件或手動調(diào)用performSegueWithIdentifier:sender:方法觸發(fā)一個Unwind Segue以后,首先UIKit會發(fā)送 canPerformUnwindSegueAction:fromViewController:withSender:消息到 sourceViewController詢問是否處理UnwindSegue的action憔晒,由于sourceViewController不能處理 (Unwind到自身沒有意義)藻肄,會返回NO

● UIKit然后會尋找sourceViewController的父Controller。如果sourceController是嵌入 Navigation View Controller的子Controller拒担,那么父Controller就是其navigationController

● 之后UIKit會發(fā)送 viewControllerForUnwindSegueAction:fromViewController:withSender:消息給 navigationController嘹屯,詢問能否找到一個負責(zé)處理此action的子Controller

● 在navigationController的默認(rèn) viewControllerForUnwindSegueAction:fromViewController:withSender:實現(xiàn) 中,navigationController會向自己的navigation棧上的所有子Controller發(fā)送

● canPerformUnwindSegueAction:fromViewController:withSender:消息从撼。 UIViewController類中州弟,該方法的默認(rèn)實現(xiàn)會查看unwinde segue action定義是否存在(即上面提到的特定簽名的方法是否存在,這個方法的內(nèi)部實現(xiàn)可以留空)低零,若存在就返回YES婆翔。

● 如果navigationController的viewControllerForUnwindSegueAction:fromViewController:withSender:方法返回nil,則不會觸發(fā)任何Unwind Segue

● 如果navgationController找到一個子類可以處理Unwind Segue的action掏婶,那么UIKit會發(fā)送 segueForUnwindingToViewController:fromViewController:identifier:消息給 navigationController啃奴,此方法將返回一個實際執(zhí)行定制轉(zhuǎn)場的segue實例

● 調(diào)用sourceViewController上的prepareForSegue:sender:方法

● 調(diào)用由viewControllerForUnwindSegueAction:fromViewController:withSender:方法返回的destinationViewController中的Segue action方法

● 調(diào)用Unwind Segue實例中的perform方法

從 上面的我們可以知道,Unwind Segue的正常工作必須要有一個Container View Controller作為所有流程View Controller的父Controller來管理整個流程雄妥。在上面的原理說明中最蕾,這個父Controller是Navigation View Controller。如果我們要實現(xiàn)一個自己的定義的Container老厌,就必須給自定義的View Controller類實現(xiàn)一些上面提到過的方法:

● canPerformUnwindSegueAction:fromViewController:withSender:

● viewControllerForUnwindSegueAction:fromViewController:withSender:

● segueForUnwindingToViewController:fromViewController:identifier:

關(guān)于這些方法的說明和實現(xiàn)方式瘟则,下面就來詳細討論一下。

自定義Container

實現(xiàn)自定義的Container View Controller

我們一般會在子Controller中通過實現(xiàn)canPerformUnwindSegueAction:fromViewController:withSender:來決定要不要執(zhí)行相應(yīng)的Unwind Segue枝秤。

在 自定義的容器中醋拧,我們必須實現(xiàn) viewControllerForUnwindSegueAction:fromViewController:withSender:和 segueForUnwindingToViewController:fromViewController:identifier:方法。前一個方法 用來決定那個View Controller來處理Unwind Segue action,后一個方法用來返回自定義的Unwind Segue實例丹壕。

使用Modal presentation時需要注意的情況

當(dāng) 我們使用UIViewController的presentViewController:animated:completion:方法以Modal presentation的方式來跳轉(zhuǎn)場景的時候庆械,情況與在Navigation View Controller有很大不同。首先雀费,使用這種方式跳轉(zhuǎn)場景的時候干奢,跳轉(zhuǎn)到的View Controller為Source View Controller的子Controller,而在Navigation View Controller中盏袄,所有的流程Controller基本上都是Navgation View Controller的子Controller忿峻,所以二者在View Controller的層次管理上有很多不同。因此實現(xiàn)Modal presentation風(fēng)格的Segue的時候辕羽,動畫的view不能搞錯逛尚,必須對View Controller中的頂層View操作。一個參考實現(xiàn)如下(略掉動畫效果代碼刁愿,僅提供轉(zhuǎn)場方法調(diào)用代碼):

Segue部分:

//
//  CAlayer.m
//  CureFunNew
//
//  Created by Hubery on 2017/9/9.
//  Copyright ? 2017年 TLQ. All rights reserved.
//
- (UIView *)findTopMostViewForViewController:(UIViewController *)viewController
{
UIView *theView = viewController.view;
UIViewController *parentViewController = viewController.parentViewController;
while (parentViewController != nil)
{
theView = parentViewController.view;
parentViewController = parentViewController.parentViewController;
}
return theView;
}
- (void)perform
{
UIViewController *source = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
// Find the views that we will be animating. If the source or destination
// view controller sits inside a container view controller, then the view
// to animate will actually be that parent controller's view.
UIView *sourceView = [self findTopMostViewForViewController:source];
UIView *destinationView = [self findTopMostViewForViewController:destination];
[source presentViewController:destination animated:NO completion:^{
// completion code here
}];
}

Unwind Segue部分:

//
//  CAlayer.m
//  CureFunNew
//
//  Created by Hubery on 2017/9/9.
//  Copyright ? 2017年 TLQ. All rights reserved.
//
- (UIView *)findTopMostViewForViewController:(UIViewController *)viewController
{
UIView *theView = viewController.view;
UIViewController *parentViewController = viewController.parentViewController;
while (parentViewController != nil)
{
theView = parentViewController.view;
parentViewController = parentViewController.parentViewController;
}
return theView;
}
- (void)perform
{
UIViewController *source = self.sourceViewController;
UIViewController *destination = self.destinationViewController;
// Find the views that we will be animating. If the source or destination
// view controller sits inside a container view controller, then the view
// to animate will actually be that parent controller's view.
UIView *sourceView = [self findTopMostViewForViewController:source];
UIView *destinationView = [self findTopMostViewForViewController:destination];
[source dismissViewControllerAnimated:NO completion:^{
// completion code here
}];
}

注 意:Modal Presentation的Unwind Segue很難實現(xiàn)無Bug的任意跳轉(zhuǎn)绰寞,因為UIViewController中,跟Container View Controller相關(guān)的方法的默認(rèn)實現(xiàn)并不能很好的定位Container View Controller铣口。而以正確的方式重寫這些方法并不容易滤钱。所以如果有任意跳轉(zhuǎn)的需求,我們可以嘗試自己實現(xiàn)一個簡單的Container View Controller脑题。

使用AddChildViewController API實現(xiàn)自己的Container View Controller

我 們偶爾會希望有一個跟Navigation View Controller差不多的容器件缸,但是又不希望像Navigation View Controller那么笨重,且限制多多叔遂。我們知道Navigation View Controller在Interface Builder中他炊,其Navigation Bar能容納的元素樣式并不豐富,盡管大多數(shù)時候已艰,我們能夠通過UIAppearance來定制一些樣式痊末,但我們希望定制能容納更加豐富的元素的 Navigation Bar,或者其他定制的導(dǎo)航界面的時候哩掺,希望能夠?qū)崿F(xiàn)一個類似的容器凿叠。我們當(dāng)然可以模仿Navigation View Controller的公開API實現(xiàn)一個差不多的東西,如果我們要很方便的使用自定義Segue和任意跳轉(zhuǎn)的Unwind Segue的話嚼吞,還需要以特定的方式實現(xiàn)上面提到的一些方法盒件。UIViewController的addChildViewController:方法同 樣可以做出類似的功能,而且相比Modal presentation誊薄,這種方式代碼更加直觀。因為使用這個API實現(xiàn)的容器锰茉,對子Controller的管理方式與Navigation View Controller類似呢蔫。

容器的部分代碼如下:

//
//  CAlayer.m
//  CureFunNew
//
//  Created by Hubery on 2017/9/9.
//  Copyright ? 2017年 TLQ. All rights reserved.
//
- (UIViewController *)viewControllerForUnwindSegueAction:(SEL)action fromViewController:(UIViewController *)fromViewController withSender:(id)sender
{
for (UIViewController *childController in self.childViewControllers) {
if ([childController canPerformUnwindSegueAction:action fromViewController:fromViewController withSender:sender]) {
return childController;
}
}
return nil;
}
- (UIStoryboardSegue *)segueForUnwindingToViewController:(UIViewController *)toViewController fromViewController:(UIViewController *)fromViewController identifier:(NSString *)identifier
{
UIStoryboardSegue *unwindSegue = [[MyLeftToRightUnwindSegue alloc] initWithIdentifier:identifier source:fromViewController destination:toViewController];
return unwindSegue;
}

Segue代碼:

//
//  CAlayer.m
//  CureFunNew
//
//  Created by Hubery on 2017/9/9.
//  Copyright ? 2017年 TLQ. All rights reserved.
//
- (BOOL)controllerInStack:(UIViewController *)controller
{
UIViewController *fromController = self.sourceViewController;
UIViewController *containerController = fromController.parentViewController;
for (UIViewController *childController in containerController.childViewControllers) {
if (childController == controller) {
return YES;
}
}
return NO;
}
- (void)perform
{
// A simple transition.
// New scene slides in from right and old scene slides out to left.
UIViewController *fromController = self.sourceViewController;
UIViewController *toController = self.destinationViewController;
UIViewController *parentController = fromController.parentViewController;
UIView *containerView = parentController.view;
[containerView addSubview:toController.view];
CGRect initialFromRect = fromController.view.frame;
CGRect initialToRect = CGRectOffset(initialFromRect, initialFromRect.size.width, 0);
CGRect finalFromRect = CGRectOffset(initialFromRect, -initialFromRect.size.width, 0);
CGRect finalToRect = initialFromRect;
toController.view.frame = initialToRect;
if (![self controllerInStack:toController]) {
// notify containment event.
[toController willMoveToParentViewController:parentController];
}
[UIView animateWithDuration:0.4f animations:^{
fromController.view.frame = finalFromRect;
toController.view.frame = finalToRect;
} completion:^(BOOL finished) {
if (![self controllerInStack:toController]) {
// Add new controller as a child controller to the container view controller
[parentController addChildViewController:toController];
// notify containment event.
[toController didMoveToParentViewController:toController];
}
[fromController.view removeFromSuperview];
}];
}

Unwind Segue代碼:

//
//  CAlayer.m
//  CureFunNew
//
//  Created by Hubery on 2017/9/9.
//  Copyright ? 2017年 TLQ. All rights reserved.
//
- (void)perform
{
// A simple transition.
// New scene slides in from left and old scene slides out to right.
UIViewController *fromController = self.sourceViewController;
UIViewController *toController = self.destinationViewController;
UIViewController *parentController = fromController.parentViewController;
UIView *containerView = parentController.view;
[containerView addSubview:toController.view];
CGRect initialFromRect = fromController.view.frame;
CGRect initialToRect = CGRectOffset(initialFromRect, -initialFromRect.size.width, 0);
CGRect finalFromRect = CGRectOffset(initialFromRect, initialFromRect.size.width, 0);
CGRect finalToRect = initialFromRect;
toController.view.frame = initialToRect;
[UIView animateWithDuration:0.4f animations:^{
fromController.view.frame = finalFromRect;
toController.view.frame = finalToRect;
} completion:^(BOOL finished) {
[fromController.view removeFromSuperview];
}];
}

當(dāng)我們定義的Container View中有需要置頂?shù)脑兀ū热缍ㄖ频膶?dǎo)航條)時,可以將addSubView:方法換成insertSubView:atIndex:方法來調(diào)整子視圖的層次。

本次只對轉(zhuǎn)場動畫進行了一些歸納總結(jié)片吊,后期會對iOS的基礎(chǔ)動畫和進階動畫進行探究和總結(jié).

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末绽昏,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子俏脊,更是在濱河造成了極大的恐慌全谤,老刑警劉巖,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件爷贫,死亡現(xiàn)場離奇詭異认然,居然都是意外死亡,警方通過查閱死者的電腦和手機漫萄,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門卷员,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人腾务,你說我怎么就攤上這事毕骡。” “怎么了岩瘦?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵未巫,是天一觀的道長。 經(jīng)常有香客問我启昧,道長叙凡,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任箫津,我火速辦了婚禮狭姨,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘苏遥。我一直安慰自己饼拍,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布田炭。 她就那樣靜靜地躺著师抄,像睡著了一般。 火紅的嫁衣襯著肌膚如雪教硫。 梳的紋絲不亂的頭發(fā)上叨吮,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天,我揣著相機與錄音瞬矩,去河邊找鬼茶鉴。 笑死,一個胖子當(dāng)著我的面吹牛景用,可吹牛的內(nèi)容都是我干的涵叮。 我是一名探鬼主播,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼割粮!你這毒婦竟也來了盾碗?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤舀瓢,失蹤者是張志新(化名)和其女友劉穎廷雅,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體京髓,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡航缀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了朵锣。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片谬盐。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖诚些,靈堂內(nèi)的尸體忽然破棺而出飞傀,到底是詐尸還是另有隱情,我是刑警寧澤诬烹,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布砸烦,位于F島的核電站,受9級特大地震影響绞吁,放射性物質(zhì)發(fā)生泄漏幢痘。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一家破、第九天 我趴在偏房一處隱蔽的房頂上張望颜说。 院中可真熱鬧,春花似錦汰聋、人聲如沸门粪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玄妈。三九已至,卻和暖如春髓梅,著一層夾襖步出監(jiān)牢的瞬間拟蜻,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工枯饿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酝锅,地道東北人。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓奢方,卻偏偏與公主長得像搔扁,于是被迫代替她去往敵國和親擒权。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

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