讓UINavigationController更好用

去年看到過美團點評技術團隊的一篇文章iOS系統中導航欄的轉場解決方案與最佳實踐,文章對系統導航欄的改造頗有意思,最近就試着寫點代碼練練手。html

項目地址:DoubleNavigationControllergit

這個庫尚未在實際項目中檢驗過,還有不少不完善或者不能知足業務需求的地方,歡迎提issue或者PR。github

些許疑問

  • 爲何要開發這個庫?

UINavigationController在蘋果官方文檔上的是這樣介紹的:objective-c

A navigation controller is a container view controller that manages one or more child view controllers in a navigation interface.bash

UINavigationController

也就是說UINavigationController是做爲UIViewController的管理者,所以它的NavigationBar不該該從屬於任何一個ViewController。可是大部分UI設計者都沒有明白蘋果設計的用意,所以在業務中常常出現一個場景:在邏輯上應該從屬於同一個NavigationController多個ViewController卻擁有不一樣的NavigationBar樣式app

不一樣頁面的開發者只能看到對本身開發的便利性,對UINavigationController的理解不到位,處處修改NavigationBar樣式,在頁面轉場過程當中NavigationBar出現了各類不可控的問題。這個問題在App支持路由後會變得更爲突出,緣由是各個頁面的跳轉關係將會很是複雜且不可預知。框架

DoubleNavigationController解決的即是這個問題,讓開發者自由地修改NavigationBar樣式,而且不用擔憂在退回到棧下ViewController後NavigationBar的樣式也被修改。簡單來講就是,咱們修改NavigationBar再也不會影響棧內現有的頁面樣式,而隻影響以後Push的新頁面。模塊化

Example

  • 爲何不設計成既不影響棧內現有的頁面樣式,也不影響新頁面?

在這裏先講一下DoubleNavigationController的兩個設計思想 「先到先得」「誰用誰修改」post

「先到先得」

先出現的頁面樣式不該該受到後出現的頁面影響。用戶在使用過程當中先看到了頁面A的樣式,接着從A頁面跳轉到B頁面,B頁面的導航欄樣式與A頁面不一樣,這時用戶再返回A頁面,從正常邏輯上來講,用戶但願看到的A頁面導航欄應該仍是以前見過的樣式,不該該受到B的影響而改變。性能

Example

「誰用誰修改」

繼續上面一個場景,用戶從頁面A到頁面B,再從頁面B跳轉到頁面C,在上一個場景下咱們知道,頁面B修改了導航欄樣式,使其與頁面A不一樣,當咱們跳轉到頁面C時,此時存在以下兩種可能:

  1. 頁面C也對剛纔B頁面修改過的導航欄樣式屬性進行了修改。

  2. 頁面C沒有對剛纔B頁面修改過的導航欄樣式屬性進行修改。

在第1種狀況下咱們很容易肯定,C頁面的導航欄樣式就應該是C頁面本身修改的樣式。那麼在第2種狀況下,C頁面導航欄應該長什麼樣?

Example

再考慮如下3種方案:跟A頁面同樣?跟UIAppearance配置同樣?跟B頁面同樣?

C頁面導航欄跟A頁面同樣?

這個方案在邏輯上就是錯誤的,由於C頁面根本不該該關心它的上上一個頁面樣式。以下圖,假設B頁面有兩條跳轉路徑A1和A2,此時C頁面的樣式就有2種可能,相信絕大多數App的設計都不會出現 「1個頁面,2種UI」 的狀況吧。

Example

C頁面導航欄跟UIAppearance配置同樣?

讓C頁面保持和UIAppearance配置一致,這裏也存在兩個問題,一個問題是若是用戶沒有配置UIAppearance怎麼辦?

還有一個更大的問題是,這麼作彷佛破壞了蘋果對於UINavigationController的定義,這就使得導航欄在邏輯上成爲了單個頁面所獨立持有的個體,在這種狀況下倒不如隱藏系統NavigationBar,每一個頁面是實現一個本身的導航欄來個更方便維護。

C頁面導航欄跟B頁面同樣?

保持和B頁面同樣,粗略一想,這和「跟A頁面同樣?」方案彷佛是差很少的,但實際上這兩種方案有着本質區別。「跟B頁面同樣」換一個更好的說法應該是「跟最近一次用戶對導航欄修改以後的樣式同樣」,也就是說C頁面只須要關注導航欄自己,而不須要關注誰修改了導航欄,這樣一來就知足來上述的設計思想 「誰用誰修改」

實現

關於這個庫的實現,筆者在這裏參考了美團點評的這篇文章iOS系統中導航欄的轉場解決方案與最佳實踐

在轉場的過程當中隱藏原有的導航欄並添加假的 NavigationBar,當轉場結束後刪除假的 NavigationBar 並恢復原有的導航欄,這一過程能夠經過 Swizzle 的方式完成,而每一個 ViewController 只須要關心自身的樣式便可。

DoubleNavigationController核心的解決方案與這篇文章提到的是同樣的,可是在實現方式和細節上可能與文章中提到的並不同,另外有一些實現細節在美團點評的這篇文章中並無過多地透露。

幾個細節

細節1:DoubleNavigationController中選擇直接NSKeyedArchiver來複制一個FakeNavigationBar而並無自定義UIView。

細節2:有些時候一個頁面的NavigationBar可能會在用戶交互過程當中動態變化,所以咱們須要記錄每一次用戶對NavigationBar外觀的修改,並在適當的時候對FakeNavigationBar外觀也進行更新。

細節3:因爲UIAppearance的原理是在UIView被添加到視圖樹後纔會去改變對象的外觀,所以在使用FakeNavigationBar以前須要再一次和當前的navigationBar進行一次UIAppearance屬性的複製。參考:iOS UIAppearance 探祕 — HyanCat's

Example

例子

clone這個倉庫,進到Example目錄下執行pod install來運行一個demo。

Example

用法

經過在ViewController中實現dbn_configNavigationController這個方法來定製導航欄樣式。

- (void)dbn_configNavigationController:(UINavigationController *)navigationController {
    [navigationController setNavigationBarHidden:NO animated:NO];
    navigationController.navigationBar.barTintColor = [UIColor whiteColor];
    navigationController.navigationBar.tintColor = [UIColor purpleColor];
    navigationController.navigationBar.titleTextAttributes = @{NSFontAttributeName: [UIFont systemFontOfSize:20], NSForegroundColorAttributeName: [UIColor redColor]};
}

- (void)dbn_configNavigationItem:(UINavigationItem *)navigationItem {
    UIBarButtonItem *btnItem = [[UIBarButtonItem alloc] initWithTitle:@"Next" style:UIBarButtonItemStylePlain target:self action:@selector(eventFromButton:)];
    navigationItem.rightBarButtonItem = btnItem;
    navigationItem.title = @"Hello";
}
複製代碼

你還可使用dbn_performBatchUpdates:這個方法來隨時更新導航欄樣式。

[self dbn_performBatchUpdates:^(UINavigationController * _Nullable navigationController) {
    if (navigationController) {
        navigationController.navigationBar.tintColor = [UIColor purpleColor];
    }
}];
複製代碼

項目地址:DoubleNavigationController

其餘開源做品

TinyPart — 模塊化框架 github | 掘金

FastKV — iOS的高性能、高實時性key-value持久化組件 github | 掘金

Coolog — 可擴展的log框架 github | 掘金

參考

iOS系統中導航欄的轉場解決方案與最佳實踐

UIKit UIAppearance - APPLE

iOS UIAppearance 探祕 — HyanCat's

相關文章
相關標籤/搜索