鍍金池/ 教程/ iOS/ View Controller 轉(zhuǎn)場
與四軸無人機的通訊
在沙盒中編寫腳本
結(jié)構(gòu)體和值類型
深入理解 CocoaPods
UICollectionView + UIKit 力學(xué)
NSString 與 Unicode
代碼簽名探析
測試
架構(gòu)
第二期-并發(fā)編程
Metal
自定義控件
iOS 中的行為
行為驅(qū)動開發(fā)
Collection View 動畫
截圖測試
MVVM 介紹
使 Mac 應(yīng)用數(shù)據(jù)腳本化
一個完整的 Core Data 應(yīng)用
插件
字符串
為 iOS 建立 Travis CI
先進(jìn)的自動布局工具箱
動畫
為 iOS 7 重新設(shè)計 App
XPC
從 NSURLConnection 到 NSURLSession
Core Data 網(wǎng)絡(luò)應(yīng)用實例
GPU 加速下的圖像處理
自定義 Core Data 遷移
子類
與調(diào)試器共舞 - LLDB 的華爾茲
圖片格式
并發(fā)編程:API 及挑戰(zhàn)
IP,TCP 和 HTTP
動畫解釋
響應(yīng)式 Android 應(yīng)用
初識 TextKit
客戶端
View-Layer 協(xié)作
回到 Mac
Android
Core Image 介紹
自定義 Formatters
Scene Kit
調(diào)試
項目介紹
Swift 的強大之處
測試并發(fā)程序
Android 通知中心
調(diào)試:案例學(xué)習(xí)
從 UIKit 到 AppKit
iOS 7 : 隱藏技巧和變通之道
安全
底層并發(fā) API
消息傳遞機制
更輕量的 View Controllers
用 SQLite 和 FMDB 替代 Core Data
字符串解析
終身學(xué)習(xí)的一代人
視頻
Playground 快速原型制作
Omni 內(nèi)部
同步數(shù)據(jù)
設(shè)計優(yōu)雅的移動游戲
繪制像素到屏幕上
相機與照片
音頻 API 一覽
交互式動畫
常見的后臺實踐
糟糕的測試
避免濫用單例
數(shù)據(jù)模型和模型對象
Core Data
字符串本地化
View Controller 轉(zhuǎn)場
照片框架
響應(yīng)式視圖
Square Register 中的擴張
DTrace
基礎(chǔ)集合類
視頻工具箱和硬件加速
字符串渲染
讓東西變得不那么糟
游戲中的多點互聯(lián)
iCloud 和 Core Data
Views
虛擬音域 - 聲音設(shè)計的藝術(shù)
導(dǎo)航應(yīng)用
線程安全類的設(shè)計
置換測試: Mock, Stub 和其他
Build 工具
KVC 和 KVO
Core Image 和視頻
Android Intents
在 iOS 上捕獲視頻
四軸無人機項目
Mach-O 可執(zhí)行文件
UI 測試
值對象
活動追蹤
依賴注入
Swift
項目管理
整潔的 Table View 代碼
Swift 方法的多面性
為什么今天安全仍然重要
Core Data 概述
Foundation
Swift 的函數(shù)式 API
iOS 7 的多任務(wù)
自定義 Collection View 布局
測試 View Controllers
訪談
收據(jù)驗證
數(shù)據(jù)同步
自定義 ViewController 容器轉(zhuǎn)場
游戲
調(diào)試核對清單
View Controller 容器
學(xué)無止境
XCTest 測試實戰(zhàn)
iOS 7
Layer 中自定義屬性的動畫
第一期-更輕量的 View Controllers
精通 iCloud 文檔存儲
代碼審查的藝術(shù):Dropbox 的故事
GPU 加速下的圖像視覺
Artsy
照片擴展
理解 Scroll Views
使用 VIPER 構(gòu)建 iOS 應(yīng)用
Android 中的 SQLite 數(shù)據(jù)庫支持
Fetch 請求
導(dǎo)入大數(shù)據(jù)集
iOS 開發(fā)者的 Android 第一課
iOS 上的相機捕捉
語言標(biāo)簽
同步案例學(xué)習(xí)
依賴注入和注解,為什么 Java 比你想象的要好
編譯器
基于 OpenCV 的人臉識別
玩轉(zhuǎn)字符串
相機工作原理
Build 過程

View Controller 轉(zhuǎn)場

自定義轉(zhuǎn)場動畫

iOS 7 中最讓我激動的特性之一就是提供了新的 API 來支持自定義 view contrioller 之間的轉(zhuǎn)場動畫。iOS 7 發(fā)布之前,我自己寫過一些 view controller 之間的轉(zhuǎn)場動畫,這是一個比較頭疼的過程,而且這種做法并不被蘋果完全地支持,尤其是如果你想讓這個轉(zhuǎn)場動畫有交互式的效果就更難了。

在繼續(xù)閱讀之前,我需要先聲明一下:這個 API 是新近才發(fā)布的,目前還沒有所謂的最佳實踐。通常來說,開發(fā)者需要探索幾個月才能得出關(guān)于新 API 的最佳實踐。因此請將本文看做對一個新 API 的探索,而非關(guān)于這個新 API 的最佳實踐介紹。如果您有更好的關(guān)于這個 API 的實踐,請不吝賜教,我們會把您的實踐更新到這篇文章中。

在開始研究新的 API 之間,我們先來看看在 iOS 7 中 navigation controller 之間的默認(rèn)的行為發(fā)生了那些改變:在 navigation controller 中,切換兩個 view controller 的動畫變得更有交互性。比方說你想要 pop 一個 view controller 出去,你可以用手指從屏幕的左邊緣開始拖動,慢慢地把當(dāng)前的 view controller 向右拖出屏幕去。

接下來,我們來看看這個新 API。很有趣的一個現(xiàn)象是,這部分 API 大量的使用了協(xié)議而不是具體的對象。這初看起來有點奇怪,但我個人更喜歡這樣的 API 設(shè)計,因為這種設(shè)計給了我們這些開發(fā)者更大的靈活性。下面,讓我們來做件簡單的事情:在 Navigation Controller 中,實現(xiàn)一個自定義的 push 動畫效果(本文中的示例代碼托管在 Github)。為了完成這個任務(wù),需要實現(xiàn) UINavigationControllerDelegate 中的新方法:

編者注 原文的作者在 Github 上面的示例代碼和文章中的代碼有一些出入(比如下面這里是 Push,但是在示例代碼中是 Pop)。如果需要,您也可以參考這個修正版示例代碼,和文章的代碼差異要小一點。

- (id<UIViewControllerAnimatedTransitioning>)
                   navigationController:(UINavigationController *)navigationController
        animationControllerForOperation:(UINavigationControllerOperation)operation
                     fromViewController:(UIViewController*)fromVC
                       toViewController:(UIViewController*)toVC
{
    if (operation == UINavigationControllerOperationPush) {
        return self.animator;
    }
    return nil;
}

從上面的代碼可以看出,我們可以根據(jù)不同的 operation(Push 或 Pop)返回不同的 animator。我們可以把 animator 存到一個屬性中,從而在多個 operation 之間實現(xiàn)共享,或者我們也可以為每個 operation 都創(chuàng)建一個新的 animator 對象,這里的靈活性很大。

為了讓動畫運行起來,我們創(chuàng)建一個自定義類,并且實現(xiàn) UIViewControllerContextTransitioning 這個協(xié)議:

@interface Animator : NSObject <UIViewControllerAnimatedTransitioning>

@end

這個協(xié)議要求我們實現(xiàn)兩個方法,其中一個定義了動畫的持續(xù)時間:

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

另一個方法描述整個動畫的執(zhí)行效果:

- (void)animateTransition:(id<UIViewControllerContextTransitioning>)transitionContext
{
    UIViewController* toViewController = [transitionContext viewControllerForKey:UITransitionContextToViewControllerKey];
    UIViewController* fromViewController = [transitionContext viewControllerForKey:UITransitionContextFromViewControllerKey];
    [[transitionContext containerView] addSubview:toViewController.view];
    toViewController.view.alpha = 0;

    [UIView animateWithDuration:[self transitionDuration:transitionContext] animations:^{
        fromViewController.view.transform = CGAffineTransformMakeScale(0.1, 0.1);
        toViewController.view.alpha = 1;
    } completion:^(BOOL finished) {
        fromViewController.view.transform = CGAffineTransformIdentity;
        [transitionContext completeTransition:![transitionContext transitionWasCancelled]];

    }];

}

從上面的例子中,你可以看到如何運用協(xié)議的:這個方法中通過接受一個類型為 id<UIViewControllerContextTransitioning> 的參數(shù),來獲取 transition context。值得注意的是,執(zhí)行完動畫之后,我們需要調(diào)用 transitionContext 的 completeTransition: 這個方法來更新 view controller 的狀態(tài)。剩下的代碼和 iOS 7 之前的一樣了,我們從 transition context 中得到了需要做轉(zhuǎn)場的兩個 view controller,然后使用最簡單的 UIView animation 來實現(xiàn)了轉(zhuǎn)場動畫。這就是全部代碼了,我們已經(jīng)實現(xiàn)了一個縮放效果的轉(zhuǎn)場動畫。

注意,這里只是為 Push 操作實現(xiàn)了自定義效果的轉(zhuǎn)場動畫,對于 Pop 操作,還是會使用默認(rèn)的滑動效果,另外,上面我們實現(xiàn)的轉(zhuǎn)場動畫無法交互,下面我們就來看看解決這個問題。

交互式的轉(zhuǎn)場動畫

想要動畫變地可以交互非常簡單,我們只需要覆蓋另一個 UINavigationControllerDelegate 的方法:

- (id <UIViewControllerInteractiveTransitioning>)navigationController:(UINavigationController*)navigationController
                          interactionControllerForAnimationController:(id <UIViewControllerAnimatedTransitioning>)animationController
{
    return self.interactionController;
}

注意,在非交互式動畫效果中,該方法返回 nil。

這里返回的 interaction controller 是 UIPercentDrivenInteractionTransition 類的一個實例,開發(fā)者不需要任何配置就可工作。我們創(chuàng)建了一個拖動手勢(Pan Recognizer),下面是處理該手勢的代碼:

if (panGestureRecognizer.state == UIGestureRecognizerStateBegan) {
    if (location.x >  CGRectGetMidX(view.bounds)) {
        navigationControllerDelegate.interactionController = [[UIPercentDrivenInteractiveTransition alloc] init];
        [self performSegueWithIdentifier:PushSegueIdentifier sender:self];
    }
} 

編者注 這里的代碼有一點示意的意思,和實際代碼有些出入,為了尊重原作者,我們沒有進(jìn)行修改,您可以參考原文在 Github 上的示例代碼進(jìn)行對比,也可以參考這個修正版示例代碼。

只有當(dāng)用戶從屏幕右半部分開始觸摸的時候,我們才把下一次動畫效果設(shè)置為交互式的(通過設(shè)置 interactionController 這個屬性來實現(xiàn)),然后執(zhí)行方法 performSegueWithIdentifier:(如果你不是使用的 storyboards,那么就直接調(diào)用 pushViewController... 這類方法)。為了讓轉(zhuǎn)場動畫持續(xù)進(jìn)行,我們需要調(diào)用 interaction controller 的一個方法:

else if (panGestureRecognizer.state == UIGestureRecognizerStateChanged) {
    CGFloat d = (translation.x / CGRectGetWidth(view.bounds)) * -1;
    [interactionController updateInteractiveTransition:d];
} 

該方法會根據(jù)用戶手指拖動的距離計算一個百分比,切換的動畫效果也隨著這個百分比來走。最酷的是,interaction controller 會和 animation controller 一起協(xié)作,我們只使用了簡單的 UIView animation 的動畫效果,但是interaction controller 卻控制了動畫的執(zhí)行進(jìn)度,我們并不需要把 interaction controller 和 animation controller 關(guān)聯(lián)起來,因為所有這些系統(tǒng)都以一種解耦的方式自動地替我們完成了。

最后,我們需要根據(jù)用戶手勢的停止?fàn)顟B(tài)來判斷該操作是結(jié)束還是取消,并調(diào)用 interaction controller 中對應(yīng)的方法:

else if (panGestureRecognizer.state == UIGestureRecognizerStateEnded) {
    if ([panGestureRecognizer velocityInView:view].x < 0) {
        [interactionController finishInteractiveTransition];
    } else {
        [interactionController cancelInteractiveTransition];
    }
    navigationControllerDelegate.interactionController = nil;
}

注意,當(dāng)切換完成或者取消的時候,記得把 interaction controller 設(shè)置為 nil。因為如果下一次的轉(zhuǎn)場是非交互的, 我們不應(yīng)該返回這個舊的 interaction controller。

現(xiàn)在我們已經(jīng)實現(xiàn)了一個完全自定義的可交互的轉(zhuǎn)場動畫了。通過簡單的手勢識別和 UIKit 提供的一個類,用幾行代碼就達(dá)到完成了。對于大部分的應(yīng)用場景,你讀到這兒就夠用了,使用上面提到的方法就可以達(dá)到你想要的動畫效果了。但如果你想更對轉(zhuǎn)場動畫或者交互效果進(jìn)行深度定制,請繼續(xù)閱讀下面一節(jié)。

使用 GPUImage 定制動畫

下面我們就來看看如何真正的,徹底的定制動畫效果。這一次我們不使用 UIView animation,甚至連 Core Animation 也不用,完全自己來實現(xiàn)所有的動畫效果。在 Letterpress-style 這個項目中,剛開始我嘗試使用 Core Image 來做動畫效果,但是在我的 iPhone 4 上,動畫的渲染最高只能達(dá)到 9 幀/秒,離我想要的 60 幀/秒差得很遠(yuǎn)。

但是當(dāng)我使用了 GPUImage 之后,實現(xiàn)一個非常漂亮的動畫變的異常簡單。這里我們要實現(xiàn)的轉(zhuǎn)場效果是:兩個 view controller 像素化,然后相互消融在一起。實現(xiàn)方法是先對兩個 view controller 進(jìn)行截屏,然后再用 GPUImage 的圖片濾鏡(filter)處理這兩張截圖。

首先,我們先創(chuàng)建一個自定義類,這個類實現(xiàn)了 UIViewControllerAnimatedTransitioningUIViewControllerInteractiveTransitioning 這兩個協(xié)議:

@interface GPUImageAnimator : NSObject
  <UIViewControllerAnimatedTransitioning,
   UIViewControllerInteractiveTransitioning>

@property (nonatomic) BOOL interactive;
@property (nonatomic) CGFloat progress;

- (void)finishInteractiveTransition;
- (void)cancelInteractiveTransition;

@end

為了加速動畫的運行,我們可以把圖片一次加載到 GPU 中,然后所有的處理和繪圖都直接在 GPU 上執(zhí)行,不需要再傳送到 CPU 處理(這種數(shù)據(jù)傳輸非常慢)。通過使用 GPUImageView,我們就可以直接使用 OpenGL 畫圖(我們不需要手寫 OpenGL 這種底層的代碼,只要繼續(xù)使用 GPUImage 封裝好的接口就可以)。

創(chuàng)建濾鏡鏈(filter chain)也非常的直觀,我們可以直接在樣例代碼的 setup 方法中看到如何構(gòu)造它。比較有挑戰(zhàn)的是如何讓濾鏡也“動”起來。GPUImage 沒有直接提供給我們動畫效果,因此我們需要每渲染一幀就更新一下濾鏡來實現(xiàn)動態(tài)的濾鏡效果。使用 CADisplayLink 可以完成這個工作:

編者注 原文中的示例代碼中缺少了這一章的內(nèi)容,我在原作者的 Github Gist 上找到了相關(guān)的源碼,整理之后放到了 Github 上,您可以在這里找到它。

self.displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(frame:)];
[self.displayLink addToRunLoop:[NSRunLoop mainRunLoop] forMode:NSRunLoopCommonModes];

frame 方法中,我們可以根據(jù)時間來更新動畫進(jìn)度,并相應(yīng)地更新濾鏡:

- (void)frame:(CADisplayLink*)link
{
    self.progress = MAX(0, MIN((link.timestamp - self.startTime) / duration, 1));
    self.blend.mix = self.progress;
    self.sourcePixellateFilter.fractionalWidthOfAPixel = self.progress *0.1;
    self.targetPixellateFilter.fractionalWidthOfAPixel = (1- self.progress)*0.1;
    [self triggerRenderOfNextFrame];
}

好了,基本上這樣就完成了。如果你想要實現(xiàn)交互式的轉(zhuǎn)場效果,那么在這里,就不能使用時間,而是要根據(jù)手勢來更新動畫進(jìn)度,其他的代碼基本差不多。

這個功能非常強大,你可以使用 GPUImage 中任何已有的濾鏡,或者寫一個自己的 OpenGL 著色器(shader)來達(dá)到你想要的效果。

結(jié)論

本文只探討了在 navigation controller 中的兩個 view controller 之間的轉(zhuǎn)場動畫,但是這些做法在 tab bar controller 或者任何你自己定義的 view controller 容器中也是通用的。另外,在 iOS 7 中,UICollectionViewController 也進(jìn)行了擴展,現(xiàn)在你可以在布局之間進(jìn)行自動以及交互的動畫切換,背后使用的也是同樣的機制。這真是太強大了。

在和 Orta 討論這個 API 的時候,他提到他已經(jīng)在大量地使用這些機制以創(chuàng)建更輕量的 view controller。與其在一個 view controller 中維護(hù)各種狀態(tài),不如再創(chuàng)建一個新的 view controller,使用自定義的轉(zhuǎn)場動畫,然后在這個轉(zhuǎn)場動畫中來移動你的各種 view。

擴展閱讀

上一篇:代碼簽名探析下一篇:圖片格式