標籤:
原文連結:http://blog.sunnyxx.com/2015/06/07/fullscreen-pop-gesture/
全屏返回手勢
自 iOS7 之後,Apple 增加了螢幕邊緣右劃返回互動的支援,再配合上 UINavigationController 的互動式動畫,pop 到上一級頁面的操作變的非常順暢和絲滑,從此,我很少再使用點擊左上方導覽列上的返回按鈕的方式返回了,因為這對單手操作十分不友好;如果一個 App 居然膽敢不支援滑動返回,那離被卸載就不遠了。
說到全屏返回手勢,首先我感覺這件事本身可能就有問題,畢竟有點反蘋果官方的互動,讓使用者從任意的地方都能夠滑動返回這個互動在國內的 App 中非常普遍,比如我手機中的手Q、微博、網易新聞、福士點評等,當然還有百度知道- -。這裡得對的產品經理們得點個贊,從整個 App 來看,不論是互動還是 UI 結構和樣式都非常的 iOS,沒有什麼特別奇葩的頁面和互動,以至於使用 UIKit 原生的架構可以非常簡單的搭建起來,這也符合我個人對 App 的一個願景:一個優秀的 App 不論從使用者角度看還是從代碼角度看都應該是簡單且優雅的,呼籲各家產品經理可以多借鑒下像這樣很本色的 App 設計。(以後可以分享下如何使用 Storyboard 在一小時內快速搭建起 UI)
FDFullscreenPopGesture
工作畢竟是工作,於是乎所以就被迫實現了套 pan 手勢處理加和視差,雖然在運動曲線上、bar 處理上下了不少功夫,但距離系統的絲滑效果還是差距挺遠。隨時間推移,終於能夠最低支援 iOS7 後,我們把這個問題再次拿出來討論和研究,直到在微博上看到了 J_雨同學的這篇文章 後才找到了這個迄今為止最簡單的解決方案。於是乎在他的授權下,我們在 forkingdog 上把這個返回手勢開源,github地址,並果斷應用到了百度知道 App 內,這是 Demo 效果:
利用了系統自己的邊緣返回手勢處理函數後,一切動畫和曲線都和原生效果一毛一樣了。
於是乎發布了 FDFullscreenPopGesture 1.0 版本,而且提供了一個 AOP 形式的 API,把它添加到工程裡面,什麼代碼都不用寫,所有 UINavigationController 就內建這個全屏返回效果了。
絲滑的處理導覽列的顯示和隱藏
接下來我們發現利用系統的 UINavigationBar 時,返回手勢中若碰到前一個頁面有 bar,後一個頁面沒 bar,或者反過來時,動畫就非常難看,舉兩個反例:
手Q iOS:
它的個人中心頁面上面的 bar 是隱藏狀態,然後做了個和其他頁面很像的假 bar,但返回手勢一開始就露餡了,為了彌補,還做了下後面真 bar 的 alpha 值動畫,兩個返回按鈕還是重疊在了一起。
新浪微博 iOS:
和手Q一樣的實現方式,只不過沒做 alpha 動畫,所以就非常明顯了。
為啥會這樣呢?這可能就是 UINavigationController 在導覽列控制 API 上設計的缺陷了。 一個 UINavigationController 管理了串列的 N 個 UIViewController 棧式的 push 和 pop,而 UINavigationBar 由 UINavigationController 管理,這就導致了 UIViewController 無法控制自己上面的 bar 單獨的隱藏或顯示。 這非常像 UIApplication 全域的 status bar,牽一髮還得動全身,不過 Apple 在 iOS7 之後為 vc 控制自己的 status bar 提供了下面幾個方法:
- (UIStatusBarStyle)preferredStatusBarStyle NS_AVAILABLE_IOS(7_0);- (BOOL)prefersStatusBarHidden NS_AVAILABLE_IOS(7_0);- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0); |
終於讓這個全域變數變成了局部變數,雖然寫起來費勁了些。
但是對 UINavigationBar 的控制,依然是全域的,可能 Apple 覺得 App 不應該有這種奇怪的頁面結構?
解決這個問題的方法也不難,在滑動返回的後要出現的那個 view controller 中寫下面的代碼:
- (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.navigationController setNavigationBarHidden:YES animated:animated];} |
系統就會把有 bar 和 無 bar 的 transition 動畫銜接起來。但是如上面所說,這是個全域變數,還得在所有由這個沒有 bar 的特殊頁面能 push 和 pop 的頁面都進行反向的處理,代碼非常的亂乎。於是乎,我們試著解決了這個問題,先看效果:
我特意挑了個從真 bar 到假 bar,再從假 bar 到 真 bar 的頁面,還算蠻絲滑的,transition 動畫全是系統自己搞定的。
就事把 FDFullscreenPopGesture 更新到了 1.1 版本,貫徹我們一向的精簡 API,你只需要在 bar 要隱藏的 view controller 中寫一句話:
- (void)viewDidLoad [super viewDidLoad]; self.navigationController.fd_prefersNavigationBarHidden = YES;} |
或者喜歡重載的寫法也行:
- (BOOL)fd_prefersNavigationBarHidden { return YES;} |
刻意的模仿了下系統的命名風格,就這一句話,剩下的就都不用操心了。
關於私人API
大家會質疑說,這用到了 UIKit 的私人屬性和私人 API,要是系統升級變了咋辦?要是審核被拒了咋辦?
首先,iOS 系統的 SDK 為了向下相容,一般只會增加方法或者修改方法實現,不太可能直接刪除一個共有方法,而私人方法的行為確實可能有變化,但系統 release 頻率畢竟很低,每當新版本發布時 check 下原來的功能是否能 work 就好了,大可不必擔心這麼遠,SDK 是死的人是活的。
另一個就是審核問題,FDFullscreenPopGesture 的實現中有主要有兩處觸碰到了私人 API:
// 1. 私人變數標誌transition動畫是否進行中[self.navigationController valueForKey:@"_isTransitioning"];// 2. 一個內部的selectorNSSelectorFromString(@"handleNavigationTransition:"); |
不論是 kvc 還是 selector 反射,都是利用 objc runtime 完成的,而到了這一層,真的就沒啥公有私人可言了。設想你就是開發 Apple 私人 API 檢查工具的工程師,給你一個 ipa 的包,你會如何檢查出其中有沒有私人 API 呢?
首先,這個檢查一定是個靜態檢查吧,不可能是運行時檢查,因為代碼邏輯那麼複雜,把程式跑起來看所有 objc_msgSend 中包不包括私人調用這件事太不現實了。
對 ipa 檔案做靜態檢查的話肯定是去分析 Mach-O 可執行檔,因為這時很多原始碼層級的資訊已經丟失,經分析可以採取下面幾種手段:
- 是否 link 了私人 framework 或者公開 framework 中的私人符號,這可以防止開發人員把私人 header 都 dump 出來供程式直接調用。
- 同上,使用
@selector(_private_sel)加上-performSelector:的方式直接調用私人 API。
- 掃描所有符號,查看是否有繼承自私人類,重載私人方法,方法名是否有重合。
- 掃描所有string,看字串常量段是否出現和私人 API 對應的。
我覺得前三條被 catch 住的可能性最高,也最容易被檢查出來。再來看我們用到用字串的方法 kvc 和 反射 selector,應該屬於最後一條,這時候就很難抉擇了,拿 handleNavigationTransition: 來說,看上去人畜無害啊,我自己類裡面的方法也完全可能命名出這個來,所以單單憑藉字串命中私人 API 判定,蘋果很容易誤傷一大票開發人員。
綜上,我覺得使用字串的方式使用私人 API 是相對安全的,我們的 App 馬上要提交審核,如果過了幾天你還能讀到這段文字,說明我的猜想是木有錯的,大家可以放心使用。
0 代碼的 Demo
還有一個有意思的事,我們在 github 上的 demo工程 木有寫一行代碼,就實現了下面的效果:
工程長這個樣子,view controller 類也沒寫,為了體現 FDFullscreenPopGesture 的 AOP 性質:
頁面由 Storyboard 構建:
而控制頁面隱藏 bar 的屬性也能用 Runtime Attributes 類比調用:
這樣就完成了一個非常乾淨的 Demo
加入到你的工程中
首先要求最低支援 iOS7,我想在 WWDC 2015 結束,iOS9 發布後,主流的 App 就都會 iOS7 起跳了。
依然是熟悉的 cocoapods 安裝:
pod ‘FDFullscreenPopGesture‘, ‘~> 1.1‘ |
要是沒有搜到就 pod setup 下。
廣告時間
我這邊正在招聘 iOS,座標北京,希望找到一個代碼規範的、愛用 IB 的、懶得寫重複代碼、不愛加班的同學,相信這裡有很大空間供你學習和提升,還可以參與到 forkingdog 開源小組中做點屌屌的東西,歡迎私聊或把簡曆丟到 [email protected]
iOS 一個絲滑的全屏滑動返回手勢