去年看到过美团点评技术团队的一篇文章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.
也就是说UINavigationController
是做为UIViewController
的管理者,所以它的NavigationBar不该该从属于任何一个ViewController。可是大部分UI设计者都没有明白苹果设计的用意,所以在业务中常常出现一个场景:在逻辑上应该从属于同一个NavigationController的多个ViewController却拥有不一样的NavigationBar样式。app
不一样页面的开发者只能看到对本身开发的便利性,对UINavigationController
的理解不到位,处处修改NavigationBar样式,在页面转场过程当中NavigationBar出现了各类不可控的问题。这个问题在App支持路由后会变得更为突出,缘由是各个页面的跳转关系将会很是复杂且不可预知。框架
DoubleNavigationController解决的即是这个问题,让开发者自由地修改NavigationBar样式,而且不用担忧在退回到栈下ViewController后NavigationBar的样式也被修改。简单来讲就是,咱们修改NavigationBar再也不会影响栈内现有的页面样式,而只影响以后Push的新页面。模块化
在这里先讲一下DoubleNavigationController的两个设计思想 “先到先得”、“谁用谁修改”。post
先出现的页面样式不该该受到后出现的页面影响。用户在使用过程当中先看到了页面A的样式,接着从A页面跳转到B页面,B页面的导航栏样式与A页面不一样,这时用户再返回A页面,从正常逻辑上来讲,用户但愿看到的A页面导航栏应该仍是以前见过的样式,不该该受到B的影响而改变。性能
继续上面一个场景,用户从页面A到页面B,再从页面B跳转到页面C,在上一个场景下咱们知道,页面B修改了导航栏样式,使其与页面A不一样,当咱们跳转到页面C时,此时存在以下两种可能:ui
在第1种状况下咱们很容易肯定,C页面的导航栏样式就应该是C页面本身修改的样式。那么在第2种状况下,C页面导航栏应该长什么样?
再考虑如下3种方案:跟A页面同样?跟UIAppearance配置同样?跟B页面同样?
C页面导航栏跟A页面同样?
这个方案在逻辑上就是错误的,由于C页面根本不该该关心它的上上一个页面样式。以下图,假设B页面有两条跳转路径A1和A2,此时C页面的样式就有2种可能,相信绝大多数App的设计都不会出现 “1个页面,2种UI” 的状况吧。
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
clone这个仓库,进到Example
目录下执行pod install
来运行一个demo。
经过在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
FastKV — iOS的高性能、高实时性key-value持久化组件
github |
掘金
Coolog — 可扩展的log框架 github |
掘金