自 iOS7 之后,Apple 增加了屏幕邊緣右劃返回交互的支持,再配合上 UINavigationController 的交互式動(dòng)畫,pop 到上一級頁面的操作變的非常順暢和絲滑,從此,我很少再使用點(diǎn)擊左上角導(dǎo)航欄上的返回按鈕的方式返回了,因?yàn)檫@對單手操作十分不友好;如果一個(gè) App 居然膽敢不支持滑動(dòng)返回,那離被卸載就不遠(yuǎn)了。
說到全屏返回手勢,首先我感覺這件事本身可能就有問題,畢竟有點(diǎn)反蘋果官方的交互,讓用戶從任意的地方都能夠滑動(dòng)返回這個(gè)交互在國內(nèi)的 App 中非常普遍,比如我手機(jī)中的手Q、微博、網(wǎng)易新聞、大眾點(diǎn)評等,當(dāng)然還有百度知道- -。這里得對微信的產(chǎn)品經(jīng)理們得點(diǎn)個(gè)贊,從整個(gè) App 來看,不論是交互還是 UI 結(jié)構(gòu)和樣式都非常的 iOS,沒有什么特別奇葩的頁面和交互,以至于使用 UIKit 原生的框架可以非常簡單的搭建起來,這也符合我個(gè)人對 App 的一個(gè)愿景:一個(gè)優(yōu)秀的 App 不論從用戶角度看還是從代碼角度看都應(yīng)該是簡單且優(yōu)雅的,呼吁各家產(chǎn)品經(jīng)理可以多借鑒下像微信這樣很本色的 App 設(shè)計(jì)。(以后可以分享下如何使用 Storyboard 在一小時(shí)內(nèi)快速搭建起微信 UI)
工作畢竟是工作,于是乎所以就被迫實(shí)現(xiàn)了套 pan 手勢處理加截圖和視差,雖然在運(yùn)動(dòng)曲線上、bar 截圖處理上下了不少功夫,但距離系統(tǒng)的絲滑效果還是差距挺遠(yuǎn)。隨時(shí)間推移,終于能夠最低支持 iOS7 后,我們把這個(gè)問題再次拿出來討論和研究,直到在微博上看到了 J_雨同學(xué)的這篇文章 后才找到了這個(gè)迄今為止最簡單的解決方案。于是乎在他的授權(quán)下,我們在 forkingdog 上把這個(gè)返回手勢開源,github地址,并果斷應(yīng)用到了百度知道 App 內(nèi),這是 Demo 效果:
利用了系統(tǒng)自己的邊緣返回手勢處理函數(shù)后,一切動(dòng)畫和曲線都和原生效果一毛一樣了。
于是乎發(fā)布了 FDFullscreenPopGesture
1.0 版本,而且提供了一個(gè) AOP 形式的 API,把它添加到工程里面,什么代碼都不用寫,所有 UINavigationController 就自帶這個(gè)全屏返回效果了。
接下來我們發(fā)現(xiàn)利用系統(tǒng)的 UINavigationBar 時(shí),返回手勢中若碰到前一個(gè)頁面有 bar,后一個(gè)頁面沒 bar,或者反過來時(shí),動(dòng)畫就非常難看,舉兩個(gè)反例:
手Q iOS:
它的個(gè)人中心頁面上面的 bar 是隱藏狀態(tài),然后做了個(gè)和其他頁面很像的假 bar,但返回手勢一開始就露餡了,為了彌補(bǔ),還做了下后面真 bar 的 alpha 值動(dòng)畫,兩個(gè)返回按鈕還是重疊在了一起。
新浪微博 iOS:
和手Q一樣的實(shí)現(xiàn)方式,只不過沒做 alpha 動(dòng)畫,所以就非常明顯了。
為啥會(huì)這樣呢?這可能就是 UINavigationController 在導(dǎo)航欄控制 API 上設(shè)計(jì)的缺陷了。 一個(gè) UINavigationController 管理了串行的 N 個(gè) UIViewController 棧式的 push 和 pop,而 UINavigationBar 由 UINavigationController 管理,這就導(dǎo)致了 UIViewController 無法控制自己上面的 bar 單獨(dú)的隱藏或顯示。 這非常像 UIapplication 全局的 status bar,牽一發(fā)還得動(dòng)全身,不過 Apple 在 iOS7 之后為 vc 控制自己的 status bar 提供了下面幾個(gè)方法:
123 | - (UIStatusBarStyle)PReferredStatusBarStyle NS_AVAILABLE_IOS(7_0);- (BOOL)prefersstatusBarHidden NS_AVAILABLE_IOS(7_0);- (UIStatusBarAnimation)preferredStatusBarUpdateAnimation NS_AVAILABLE_IOS(7_0); |
終于讓這個(gè)全局變量變成了局部變量,雖然寫起來費(fèi)勁了些。
但是對 UINavigationBar 的控制,依然是全局的,可能 Apple 覺得 App 不應(yīng)該有這種奇怪的頁面結(jié)構(gòu)?
解決這個(gè)問題的方法也不難,在滑動(dòng)返回的后要出現(xiàn)的那個(gè) view controller 中寫下面的代碼:
1234 | - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [self.navigationController setNavigationBarHidden:YES animated:animated];} |
系統(tǒng)就會(huì)把有 bar 和 無 bar 的 transition 動(dòng)畫銜接起來。但是如上面所說,這是個(gè)全局變量,還得在所有由這個(gè)沒有 bar 的特殊頁面能 push 和 pop 的頁面都進(jìn)行反向的處理,代碼非常的亂乎。于是乎,我們試著解決了這個(gè)問題,先看效果:
我特意挑了個(gè)從真 bar 到假 bar,再從假 bar 到 真 bar 的頁面,還算蠻絲滑的,transition 動(dòng)畫全是系統(tǒng)自己搞定的。
就事把 FDFullscreenPopGesture
更新到了 1.1 版本,貫徹我們一向的精簡 API,你只需要在 bar 要隱藏的 view controller 中寫一句話:
1234 | - (void)viewDidLoad [super viewDidLoad]; self.navigationController.fd_prefersNavigationBarHidden = YES;} |
或者喜歡重載的寫法也行:
123 | - (BOOL)fd_prefersNavigationBarHidden { return YES;} |
刻意的模仿了下系統(tǒng)的命名風(fēng)格,就這一句話,剩下的就都不用操心了。
大家會(huì)質(zhì)疑說,這用到了 UIKit 的私有屬性和私有 API,要是系統(tǒng)升級變了咋辦?要是審核被拒了咋辦?
首先,iOS 系統(tǒng)的 SDK 為了向下兼容,一般只會(huì)增加方法或者修改方法實(shí)現(xiàn),不太可能直接刪除一個(gè)共有方法,而私有方法的行為確實(shí)可能有變化,但系統(tǒng) release 頻率畢竟很低,每當(dāng)新版本發(fā)布時(shí) check 下原來的功能是否能 work 就好了,大可不必?fù)?dān)心這么遠(yuǎn),SDK 是死的人是活的。
另一個(gè)就是審核問題,F(xiàn)DFullscreenPopGesture 的實(shí)現(xiàn)中有主要有兩處觸碰到了私有 API:
1234 | // 1. 私有變量標(biāo)志transition動(dòng)畫是否正在進(jìn)行[self.navigationController valueForKey:@"_isTransitioning"];// 2. 一個(gè)內(nèi)部的selectorNSSelectorFromString(@"handleNavigationTransition:"); |
不論是 kvc 還是 selector 反射,都是利用 objc runtime 完成的,而到了這一層,真的就沒啥公有私有可言了。設(shè)想你就是開發(fā) Apple 私有 API 檢查工具的工程師,給你一個(gè) ipa 的包,你會(huì)如何檢查出其中有沒有私有 API 呢?
首先,這個(gè)檢查一定是個(gè)靜態(tài)檢查吧,不可能是運(yùn)行時(shí)檢查,因?yàn)榇a邏輯那么復(fù)雜,把程序跑起來看所有 objc_msgSend 中包不包括私有調(diào)用這件事太不現(xiàn)實(shí)了。
對 ipa 文件做靜態(tài)檢查的話肯定是去分析 Mach-O 可執(zhí)行文件,因?yàn)檫@時(shí)很多源代碼級別的信息已經(jīng)丟失,經(jīng)分析可以采取下面幾種手段:
@selector(_private_sel)
加上-performSelector:
的方式直接調(diào)用私有 API。我覺得前三條被 catch 住的可能性最高,也最容易被檢查出來。再來看我們用到用字符串的方法 kvc 和 反射 selector,應(yīng)該屬于最后一條,這時(shí)候就很難抉擇了,拿 handleNavigationTransition:
來說,看上去人畜無害啊,我自己類里面的方法也完全可能命名出這個(gè)來,所以單單憑借字符串命中私有 API 判定,蘋果很容易誤傷一大票開發(fā)者。
綜上,我覺得使用字符串的方式使用私有 API 是相對安全的,我們的 App 馬上要提交審核,如果過了幾天你還能讀到這段文字,說明我的猜想是木有錯(cuò)的,大家可以放心使用。
還有一個(gè)有意思的事,我們在 github 上的 demo工程 木有寫一行代碼,就實(shí)現(xiàn)了下面的效果:
工程長這個(gè)樣子,view controller 類也沒寫,為了體現(xiàn) FDFullscreenPopGesture
的 AOP 性質(zhì):
頁面由 Storyboard 構(gòu)建:
而控制頁面隱藏 bar 的屬性也能用 Runtime Attributes 模擬調(diào)用:
這樣就完成了一個(gè)非常干凈的 Demo
首先要求最低支持 iOS7,我想在 WWDC 2015 結(jié)束,iOS9 發(fā)布后,主流的 App 就都會(huì) iOS7 起跳了。
依然是熟悉的 cocoapods 安裝:
1 | pod 'FDFullscreenPopGesture', '~> 1.1' |
要是沒有搜到就 pod setup
下。
感謝Sunnyxx大神,已經(jīng)集成到現(xiàn)有的工程里了,很好用!!!!原帖地址:http://blog.sunnyxx.com/2015/06/07/fullscreen-pop-gesture/
新聞熱點(diǎn)
疑難解答
圖片精選