感謝翻譯小組成員
dingdaojun熱心翻譯。本篇文章是咱們每週推薦優秀國外的技術類文章的其中一篇。若是您有不錯的原創或譯文,歡迎提交給咱們,更歡迎其餘朋友加入咱們的翻譯小組(聯繫qq:2408167315)。
在iOS7之前,開發者若是但願定製導航控制器推入推出視圖時的轉場動畫,通常都只能經過子類化UINavigationController或者本身編寫動畫代碼去覆蓋相應的方法,如今iOS7爲開發者帶來了福音,蘋果公司引入了大量新API,給予了開發者很高的自由度,在處理由UIViewController管理的UIView動畫時,這些API使用方便,可擴展性也很強,定製起來很是輕鬆:
• 全新的針對UIView的動畫block方法
• 全新的UIViewControllerAnimatedTransitioning協議以及動畫控制器的概念
• Interaction Controllers以及Transition Coordinators
• 全新的針對動畫的助手API(簡便方法)
這裏我編寫了一個示例應用程序,其中展現了我將在這篇文章中所提到的一些技巧, 爲了快速理解咱們應當如何使用iOS7的新API來處理
UIViewController的轉場動畫,請在此連接中下載該示例。
全新的針對UIView的動畫block方法
iOS4的發佈帶來了強大的block方法,在編寫UIView動畫時使用block能夠輕鬆地獲得滿意的效果,然而有些狀況下,咱們仍是不得不直接使用Core Animation。幸運的是,蘋果公司在iOS7中增長了2個新的基於block的方法,這樣咱們就不多再須要直接使用Core Animation了。
關鍵幀動畫
iOS7爲UIView封裝了一組API,讓咱們很容易的獲得與Core Animation框架中的CAKeyframeAnimation同樣的效果。
- [UIView animateKeyframesWithDuration:duration delay:delay
- options:options animations:^{
- [UIView addKeyframeWithRelativeStartTime:0.0
- relativeDuration:0.5 animations:^{
-
- }];
- [UIView addKeyframeWithRelativeStartTime:0.5
- relativeDuration:0.5 animations:^{
-
- }];
- } completion:^(BOOL finished) {
-
- }];
新引入的animateKeyframesWithDuration與CAKeyframeAnimation的關係,能夠比對animateWithDuration和CABasicAnimation,咱們只須要將每一幀動畫加入到block方法中,並傳入此段動畫在全過程當中的相對開始時間和執行時間(duration具體是指此段動畫的執行時間佔全過程的百分比)。同時,你能夠在一次動畫中使用多個關鍵幀,只需使用addKeyframe依次將全部關鍵幀加入動畫執行棧中。
下面是一個簡單的例子:在示例應用中,我使用關鍵幀block來退出模態視圖控制器。
- [UIView addKeyframeWithRelativeStartTime:0.0
- relativeDuration:0.15 animations:^{
-
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- -1.5);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.15
- relativeDuration:0.10 animations:^{
-
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- 1.0);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.25
- relativeDuration:0.20 animations:^{
-
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- 1.3);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.45
- relativeDuration:0.20 animations:^{
-
- snapshot.transform = CGAffineTransformMakeRotation(M_PI *
- 0.8);
- }];
- [UIView addKeyframeWithRelativeStartTime:0.65
- relativeDuration:0.35 animations:^{
-
-
- CGAffineTransform shift =
- CGAffineTransformMakeTranslation(180.0, 0.0);
- CGAffineTransform rotate =
- CGAffineTransformMakeRotation(M_PI * 0.3);
- snapshot.transform = CGAffineTransformConcat(shift,
- rotate);
- _coverView.alpha = 0.0;
- }];
視圖彷彿在重力的牽引下繞左下角順時針旋轉,並在最低點擺動了一下,最後脫落。
彈簧動畫
iOS7新引入的另外一個block方法可讓你輕鬆將真實物理世界中的彈性效果集成進視圖動畫中。蘋果公司一直建議開發者儘量將動畫效果作的跟真實物理世界同樣——在視圖滑動時,能夠像彈簧同樣,稍微拉伸一些,再彈回正確位置。使用新的彈簧動畫API來實現此效果相較以往要簡單不少。
- [UIView animateWithDuration:duration delay:delay
- usingSpringWithDamping:damping initialSpringVelocity:velocity
- options:options animations:^{
-
- } completion:^(BOOL finished) {
-
- }];
這裏用到了一些物理上的概念:damping參數表明彈性阻尼,隨着阻尼值愈來愈接近0.0,動畫的彈性效果會愈來愈明顯,而若是設置阻尼值爲1.0,則視圖動畫不會有彈性效果——視圖滑動時會直接減速到0並馬上中止,不會有彈簧類的拉伸效果。
velocity參數表明彈性修正速度,它表示視圖在彈跳時恢復原位的速度,例如,若是在動畫中視圖被拉伸的最大距離是200像素,你想讓視圖以100像素每秒的速度恢復原位,那麼就設置velocity的值爲0.5。(譯者:建議你們看看源代碼,代碼中damping設置爲0.8不夠明顯,你能夠將damping調爲0.1,而後慢慢調整velocity看看效果)
在示例應用程序中,我用彈簧動畫讓模態視圖控制器從屏幕底部滑上來,設置彈性阻尼爲0.8,彈性修正速度爲1.0,運行後能夠看到,視圖將衝出15像素的距離,而後慢慢降回原位。若是我設置彈性阻尼爲0.6或者更小,那麼視圖會衝得更高,並且降回原位前還會繼續向下反彈。(也就是中止前來回彈的次數愈來愈多,彈性效果愈來愈明顯)須要注意的是,不要將彈性動畫與UIKit的動態特效引擎相混淆。彈性動畫是一個標準的UIView動畫API,僅僅提供了有限的幾種真實物理效果。
自定義UIViewController的轉場動畫
如今讓咱們來看一個好東西。蘋果公司不只爲開發者引入了新的動畫API,並且還擴大了其應用範圍。在使用UIViewController管理視圖的推入推出時,能夠很容易地自定義如下轉場動畫:
• UIViewController
• presentViewController
• UITabBarController
• setSelectedViewController
• setSelectedIndex
• UINavigationController
• pushViewController
• popViewController
• setViewControllers
在示例應用程序中,我建立了一系列轉場動畫,在動畫中使用了以前講解過的新引入的彈簧動畫和關鍵幀block方法,如今讓咱們來看看如何使用新API來自定義上述的轉場動畫。
核心概念:動畫控制器
那麼,如何在使用自定義動畫的同時不影響視圖的其餘屬性?對此蘋果公司提供了一個新的協議:UIViewControllerAnimatedTransitioning,咱們能夠在協議方法中編寫自定義的動畫代碼。蘋果開發者文檔中稱實現了此協議的對象爲動畫控制器。
因爲咱們使用了協議這一語法特性,自定義動畫的代碼能夠靈活的放在本身想要的位置。你能夠建立一個專門用於管理動畫的類, 也可讓UIViewController實現UIViewControllerAnimatedTransitioning接口。因爲須要實現一系列不一樣的動畫,所以選擇爲每一個動畫建立一個類。接下來建立這些動畫類的通用父類——BaseAnimation,它定義了一些通用的屬性和助手方法。
讓咱們來看第一個動畫,使用UINavigationController推入推出視圖時,會有一個簡單的縮放效果。
- -(void)animateTransition:
- (id)transitionContext {
-
- UIView *containerView = [transitionContext
- containerView];
- UIViewController *fromViewController = [transitionContext
- viewControllerForKey:UITransitionContextFromViewControllerKey
- ];
- UIViewController *toViewController = [transitionContext
- viewControllerForKey:UITransitionContextToViewControllerKey];
- if (self.type == AnimationTypePresent) {
-
- toViewController.view.transform =
- CGAffineTransformMakeScale(0.0, 0.0);
- [containerView insertSubview:toViewController.view
- aboveSubview:fromViewController.view];
-
- [UIView animateWithDuration:[self
- transitionDuration:transitionContext] animations:^{
- toViewController.view.transform =
- CGAffineTransformMakeScale(1.0, 1.0);
- } completion:^(BOOL finished) {
- [transitionContext completeTransition:YES];
- }];
- } else if (self.type == AnimationTypeDismiss) {
-
- [containerView insertSubview:toViewController.view
- belowSubview:fromViewController.view];
-
- [UIView animateWithDuration:[self
- transitionDuration:transitionContext] animations:^{
- fromViewController.view.transform =
- CGAffineTransformMakeScale(0.0, 0.0);
- } completion:^(BOOL finished) {
- [transitionContext completeTransition:YES];
- }];
- }
- }
- -(NSTimeInterval)transitionDuration:
- (id)transitionContext {
- return 0.4;
- }
符合UIViewControllerAnimatedTransitioning協議的任何對象都須要實現animateTransition:和transitionDuration:兩個方法。你也能夠選擇實現@optional方法animationEnded:,它在動畫完成後由系統自動調用,至關於completion block,很是方便。
在animateTransition:中你須要處理如下過程:
1. 將「to」視圖插入容器視圖
2. 將「to」和「from」視圖分別移動到本身想要的位置
3. 最後,在動畫完成時千萬別忘了調用completeTransition: 方法
UIViewControllerAnimatedTransitioning協議中的方法都帶有一個參數:transitionContext,這是一個系統級的對象,它符合 UIView-ControllerContextTransitioning協議,咱們能夠從該對象中獲取用於控制轉場動畫的必要信息,主要包括如下內容:
顯然,蘋果公司幫助開發者完成了大部分讓人討厭的細節工做,僅僅須要咱們本身完成的工做就是定義動畫的初始狀態和終止狀態,並調整到本身滿意的效果。最後我再囉嗦兩句有關transitionContext的重要注意事項:
1.獲取frame的方法可能會返回CGRectZero——若是系統沒法肯定該frame的值具體是什麼。例如,若是你使用自定義的模態視圖控制器
推出動畫,在結束時系統沒法肯定其finalFrame。
2.若是視圖控制器已經從屏幕上移除了,那麼獲取frame的方法也會返回CGRectZero。例如在導航控制器的轉場動畫結束後,試圖獲取「from」視圖的finalFrame。
你不用手動去移除「from」視圖,transitionContext將自動幫你完成。
3.若是你在應用的其餘地方須要使用transitionContext,你能夠放心地使用動畫控制器保留一個transitionContext的引用。
將動畫控制器應用到轉場動畫中。
如今,咱們已經開發好了動畫控制器,那麼最後須要作的就是,將它們應用到轉場動畫中:咱們須要對管理轉場動畫的UIViewController作一些操做。
通常來講,咱們只須要讓UIViewController符合UIViewController-TransitioningDelegate 協議, 編寫animationController-ForPresentedController和animationControllerForDismissedController方法。在個人示例應用程序中,我設置了一個屬性,用來讓動畫控制器知道目前正在推入仍是推出視圖:
- -(id)
- animationControllerForPresentedController:(UIViewController
- *)presented presentingController:(UIViewController
- *)presenting sourceController:(UIViewController *)source {
- modalAnimationController.type = AnimationTypePresent;
- return modalAnimationController;
- }
- -(id)
- animationControllerForDismissedController:(UIViewController
- *)dismissed {
- modalAnimationController.type = AnimationTypeDismiss;
- return modalAnimationController;
- }
而後,在推入模態視圖控制器時,咱們設置modalPresentationStyle爲UIModalPresentationFullScreen或UIModalPresentationCustom。咱們還必須將一個符合UIViewControllerTransitioningDelegate協議的對象設置爲它的transitioningDelegate,通常來講都是推入該模態視圖控制器的UIViewController。
- OptionsViewController *modal = [[OptionsViewController alloc]
- initWithNibName:@"OptionsViewController" bundle:[NSBundle
- mainBundle]];
- modal.transitioningDelegate = self;
- modal.modalPresentationStyle = UIModalPresentationCustom;
- [self presentViewController:modal animated:YES
- completion:nil];
若是須要將動畫控制器應用到UINavigationController的轉場動畫中,咱們須要使用UINavigationControllerDelegate協議中的一個新方法:animationControllerForOperation。對於任何自定義的導航轉場動畫,導航欄都會有一個淡入淡出的動畫過程。一樣,對於UITabBarController,使用UITabBarControllerDelegate協議的新方法——animationController-ForTransitionFromViewController。
爲轉場動畫定義交互方式
在iOS7中,蘋果處處都在使用交互式彈出手勢,同時,蘋果也給開發者們提供了一系列工具,只需簡單幾步就能將交互手勢應用在視圖切換過程當中。咱們能夠經過相應的委託方法返回一個交互控制器:
• UINavigationController
• interactionControllerForAnimationController
• UITabBarController
• interactionControllerForAnimationController
• UIViewController
• interactionControllerForPresentation
• interactionControllerForDismissal
這裏惟一須要注意的是,若是沒有自定義轉場動畫,這些方法就不會起做用。例如,你必須從animationControllerForOperation獲得一個有效的動畫控制器,UINavigationController纔會調用interactionController-
ForAnimationController——即便你在轉場交互中沒有使用動畫控制器。
其次,交互控制器很是靈活,有很強的可擴展性。雖然在示例應用程序中我使用手勢檢測來控制交互,可是你也能夠用手勢之外的其餘方式來實現。你能夠設計任意你想要的效果用以轉場交互。
交互控制器:最簡單的實現方式有兩種方式能夠建立交互控制器。第一個也是最簡單的一個,就是使用UIPercentDrivenInteractiveTransition。
- @interface UIPercentDrivenInteractiveTransition : NSObject
-
- @property (readonly) CGFloat duration;
- @property (readonly) CGFloat percentComplete;
- @property (nonatomic,assign) CGFloat completionSpeed;
- @property (nonatomic,assign) UIViewAnimationCurve
- completionCurve;
- - (void)updateInteractiveTransition:(CGFloat)percentComplete;
- - (void)cancelInteractiveTransition;
- - (void)finishInteractiveTransition;
這個類具體實現了UIViewControllerInteractiveTransitioning協議,咱們可使用它輕鬆爲動畫控制器添加自定義的交互方式。只要爲目標視圖加入手勢(或者其餘交互方式)並調用updateInteractiveTransition:,傳入動畫時間佔整個過程的百分比便可。同時, 記住在交互完成後調用finishInteractiveTransition: , 交互被取消時調用cancel-InteractiveTransition:。下面的例子展現瞭如何將捏合手勢應用到轉場動畫中:
- -(void)handlePinch:(UIPinchGestureRecognizer *)pinch {
- CGFloat scale = pinch.scale;
- switch (pinch.state) {
- case UIGestureRecognizerStateBegan: {
- _startScale = scale;
- self.interactive = YES;
- [self.navigationController
- popViewControllerAnimated:YES];
- break;
- }
- case UIGestureRecognizerStateChanged: {
- CGFloat percent = (1.0 - scale/_startScale);
- [self updateInteractiveTransition:(percent < 0.0) ?
- 0.0 : percent];
- break;
- }
- case UIGestureRecognizerStateEnded: {
- CGFloat percent = (1.0 - scale/_startScale);
- BOOL cancelled = ([pinch velocity] < 5.0 && percent
- <= 0.3);
- if (cancelled) [self cancelInteractiveTransition];
- else [self finishInteractiveTransition];
- break;
- }
- case UIGestureRecognizerStateCancelled: {
- CGFloat percent = (1.0 - scale/_startScale);
- BOOL cancelled = ([pinch velocity] < 5.0 && percent
- <= 0.3);
- if (cancelled) [self cancelInteractiveTransition];
- else [self finishInteractiveTransition];
- break;
- }
- }
- }
當你繼承了UIPercentDrivenInteractiveTransition類,交互過程當中系統會自動調用動畫控制器的animateTransition:方法,按照你傳遞的percentComplete參數實時地展示動畫效果。在交互完成後,它還自動調用animateTransition:方法恢復到正常狀態,一旦交互完成,咱們就能夠改變completionSpeed和completionCurve屬性來修改其餘的一些樣式。
交互控制器:經過自定義的方式
若是你須要深刻控制UIPercentDrivenInteractiveTransition處理轉場動畫的細節,那麼就不用去繼承該類,而是使用UIViewController-InteractiveTransitioning協議。此協議與UIViewController-AnimatedTransitioning相似,咱們能夠經過該協議控制全部關於轉場動畫的細節。在該協議中咱們須要完成如下步驟:
1. 實現startInteractiveTransition:方法,用於初始化專場動畫。
2. 獲取transitionContext 對象的引用(若是繼承了UIPercentDrivenInteractiveTransition,能夠看到它自動幫咱們完成了這一步驟,所以這裏咱們必須手動獲取該對象)。
3. 和以前同樣,在適當的狀況下調用updateInteractiveTransition:,cancelInteractiveTransition和finishInteractiveTransition(對於導航控制器來講,完成方法中還須要顯示或隱藏導航欄)。
4. 完成後仍然請記住調用transitionCompleted:。
下面是我經過自定義的交互控制器來實現與以前相同的動畫,仍然是使用捏合手勢控制轉場動畫。
- -(void)startInteractiveTransition:
- (id)transitionContext {
-
- _context = transitionContext;
-
- UIView *containerView = [transitionContext
- containerView];
- UIViewController *fromViewController = [transitionContext
- viewControllerForKey:UITransitionContextFromViewControllerKey
- ];
- UIViewController *toViewController = [transitionContext
- viewControllerForKey:UITransitionContextToViewControllerKey];
-
- toViewController.view.frame = [transitionContext
- finalFrameForViewController:toViewController];
- [containerView insertSubview:toViewController.view
- belowSubview:fromViewController.view];
-
- _transitioningView = fromViewController.view;
- }
- -(void)updateWithPercent:(CGFloat)percent {
- CGFloat scale = fabsf(percent-1.0);
- _transitioningView.transform =
- CGAffineTransformMakeScale(scale, scale);
- [_context updateInteractiveTransition:percent];
- }
- -(void)end:(BOOL)cancelled {
- if (cancelled) {
- [UIView animateWithDuration:_completionSpeed
- animations:^{
- _transitioningView.transform =
- CGAffineTransformMakeScale(1.0, 1.0);
- } completion:^(BOOL finished) {
- [_context cancelInteractiveTransition];
- [_context completeTransition:NO];
- }];
- } else {
- [UIView animateWithDuration:_completionSpeed
- animations:^{
- _transitioningView.transform =
- CGAffineTransformMakeScale(0.0, 0.0);
- } completion:^(BOOL finished) {
- [_context finishInteractiveTransition];
- [_context completeTransition:YES];
- }];
- }
- }
你可讓動畫控制器同時實現UIViewControllerInteractive-Transitioning和 UIViewControllerAnimatedTransitioning(像示例程序中那樣),從而把全部代碼都放在一個類中。你也能夠將交互控制器和動畫控制器分紅兩個類——協議這一語法特性的妙處在於,你能夠輕鬆實現符合需求的最佳解決方案。
更多小技巧
在block中選擇是否進行動畫
開發者或許會遇到這樣一種狀況:在一串精美的動畫效果中,咱們須要讓某些視圖不進行動畫,從而營造一種動靜相宜的效果。在動畫block方法推出以前,咱們能夠在[UIView beginAnimations]和[UIView commitAnimations]之間使用setAnimationsEnabled方法來設置哪些動畫不須要執行。而在iOS7SDK中,蘋果公司爲開發者提供了新方法,只要把不須要執行的動畫寫在block中便可:
- [UIView performWithoutAnimation:^{
-
- }];
你能夠隨時執行這段代碼來控制不須要執行的動畫。
集合視圖的導航轉場動畫
你可能對UICollectionView的setLayout:animated:方法很是熟悉了。在iOS7中,當導航控制器推入推出集合視圖控制器時,若是開啓了 useLayout-ToLayoutNavigationTransitions屬性,系統將自動調用setLayout:animated:方法。所以,在你推入集合視圖控制器時,只須要設置該屬性,導航控制器就能夠自動執行動畫,和你手動對集合視圖調用setLayout:animated方法的效果同樣。
- CollectionViewController *VC = [[CollectionViewController
- alloc] initWithCollectionViewLayout:flowLayout];
- VC.title = @"Mini Apples";
- VC.useLayoutToLayoutNavigationTransitions = YES;
- [self.navigationController pushViewController:VC
- animated:YES];
轉場動畫調度器
還有一個很是有用的API, 它能夠幫助視圖控制器管理轉場動畫:UIViewControllerTransitionCoordinator協議。在iOS7中,每個視圖控制器(固然也包括UINavigationController和UITabBarController)都有一個transitionCoordinator屬性,該屬性提供了一系列用於轉場動畫的強大工具,首先咱們來看看animateAlongsideTransition:方法。
- [self.transitionCoordinator
- animateAlongsideTransition:^(id<uiviewcontrollertransitioncoo < span="">
- rdinatorContext> context) {
-
- }
- completion:^(id
- context) {
-
- }];
咱們能夠經過這個方法在進行轉場動畫時並行執行一些其餘動畫,context參數和以前提到的符合UIViewControllerContextTransitioning協議的transitionContext參數相相似,從該參數中咱們能夠獲取有關轉場過程的一些重要信息,包括container view和轉場效果。蘋果公司甚至容許開發者不傳入context參數,只傳入完成後執行的block。因此請大膽嘗試使用它吧。
對於交互轉場來講, 視圖在轉場過程當中狀態可能發生改變, 因而notifyWhenInteractionEndsUsingBlock:方法特別有用——它能夠用來管理視圖狀態。在交互轉場中,viewWillAppear:方法或許會在某個視圖控制器推入時被調用,可是按照常理隨後應該會被調用的viewDidAppear:則不必定,由於用戶隨時可能取消該交互(例如在以前的例子中,捏到一半又恢復原狀)。
由此,若是咱們不但願在這種狀況下修改視圖狀態,咱們可使用該方法,恢復對視圖的更改(使用UIViewControllerTransitionCoordinatorContext的isCancelled屬性)。
- [self.transitionCoordinator
- notifyWhenInteractionEndsUsingBlock:^(id<uiviewcontrollertran < span="">
- sitionCoordinatorContext> context) {
-
- }];
屏幕快照
在iOS7 之前, 獲取一個UIView的快照有如下步驟: 首先建立一個UIGraphics的圖像上下文,而後將視圖的layer渲染到該上下文中,從而取得一個圖像,最後關閉圖像上下文,並將圖像顯示在UIImageView中。如今咱們只須要一行代碼就能夠完成上述步驟了:
- [view snapshotViewAfterScreenUpdates:NO];
這個方法制做了一個UIView的副本,若是咱們但願視圖在執行動畫以前保存如今的外觀,以備以後使用(動畫中視圖可能會被子視圖遮蓋或者發生其餘一些變化),該方法就特別方便。
afterUpdates參數表示是否在全部效果應用在視圖上了之後再獲取快照。例如,若是該參數爲NO,則立馬獲取該視圖如今狀態的快照,反之,如下代碼只能獲得一個空白快照:
- [view snapshotViewAfterScreenUpdates:YES];
- [view setAlpha:0.0];
因爲咱們設置afterUpdates參數爲YES,而視圖的透明度值被設置成了0,因此方法將在該設置應用在視圖上了以後才進行快照,因而乎屏幕空空如也。另外就是……你能夠對快照再進行快照……繼續快照……
結論
蘋果公司在iOS7中爲開發者添加了新的用於建立和自定義動畫的API。iOS7 SDK不只引入了強大的動畫block和許多易於使用的方法,並且完全改變了爲視圖自定義動畫的方式。最後,強烈建議你將示例應用程序的代碼下載下來,看看我在本文中介紹的API的使用方法!