不管任何App,對于跳轉(zhuǎn)這個最基礎(chǔ)的動畫都是必須的,而iOS的轉(zhuǎn)場動畫不僅只是動畫鸥昏,還涉及層級存和、跳轉(zhuǎn)限制、攜帶參數(shù)...等等一系列問題蜓席。所以捋清楚專場動畫的不同和使用方式顯得尤為重要,現(xiàn)在總結(jié)一下涉及該部分的技術(shù)參數(shù)內(nèi)容如下:
Modal presentation
- 設(shè)定ViewController的modalTransitionStyle屬性器一。
這種方式也對應(yīng)Storyboard中對應(yīng)的segue transition的設(shè)置。這個屬性是一個枚舉類型厨内,其值代表已經(jīng)定制的幾種轉(zhuǎn)場風(fēng)格祈秕。這種方式也是最簡單的轉(zhuǎn)場渺贤,不帶有任何自定義的轉(zhuǎn)場效果代碼。 - 使用UIView的animation API實現(xiàn)自定義的動畫请毛。
這種方式是比較常見實現(xiàn)方式志鞍。除了官方的文檔以外,大量的Blog文章都會詳細講解這些API的用法方仿。UIView的animation API的使用比較直觀固棚,相對來說也是一種比較容易學(xué)習(xí)的動畫實現(xiàn)方式。
Navigation View Controller presentation
- 使用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相同匣掸。
- 使用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é).