共计 3766 个字符,预计需要花费 10 分钟才能阅读完成。
1. 背景
在 iOS 开发中每个页面都有可能被个性化设计,但如果页面是以 push 方式进行管理,那么多个视图控制器共享一个导航栏,导航栏的适配显示就是一个问题。因此需基于系统导航进一步调整和修改才能满足需求。本文参考下面两篇博客进行分析梳理。
Kenshin Cui’s Blog
美团技术团队
2. 关注点
页面样式自定义(包括隐藏或显示导航栏)之后,关注点如下:
导航栏内容 Title 和 Item 容易编码维护。
页面过渡导航栏内容渐变动画(参见系统导航效果)。
页面过渡导航栏背景颜色变化不突兀。
支持滑动手势 pop。
3. 导航配置
导航栏透明
self.navigationBar.isTranslucent = true // 需要开启半透明
self.navigationBar.setBackgroundImage(UIImage(), for: .default)
self.navigationBar.shadowImage = UIImage()
导航栏隐藏
// 导航栏显示(含 animated,否则页面有无导航切换可能会突变,在手势 pop 时最明显)
self.navigationController?.setNavigationBarHidden(true, animated: true)
导航栏颜色
导航栏半透明开启:既然开启半透明一般是想用模糊效果的,因此明显应使用下列第①种:
// ① 半透明开启,此种方式设置颜色有明显模糊效果, 展开图层树 UINavigatuionBar -> background 视图 -> UIVisualEffectView -> UIVisualEffectBackdropView,发现进行 UIVisualEffectBackdropView 颜色变化(箭头代表内部子视图),但是因为 UIVisualEffectView 是模糊控制视图,因此会有模糊效果显现出来
self.navigationController?.navigationBar.backgroundColor = UIColor.kcRed
// ② 半透明开启,此种方式设置颜色没有模糊效果,展开图层树 UINavigationBar ->background 视图 -> imageView,发现 imageView 颜色变化(箭头代表内部子视图)
self.navigationController?.navigationBar.setBackgroundImage(UIImage(color:UIColor.kcRed), for: .default)
// ③ 半透明开启,此种方式设置颜色有轻微模糊感,但不如第一种那样明显,展开图层树 UINavigatuionBar -> background 视图 -> UIVisualEffectView -> _UIVisualEffectSubview,发现_UIVisualEffectSubview 颜色变化(箭头代表内部子视图),因为 UIVisualEffectView 视模糊控制视图,因此会有模糊效果显现出来
self.navigationController?.navigationBar.barTintColor = UIColor.kcRed
导航栏半透明关闭:建议采用第②种
// ① 半透明关闭,此种方式不能设置导航栏背景颜色,展开图层树发现设置 backgroundcolor 仅仅影响 UINavigationBar 的颜色,但是 UINavigationBar 有一个 background 子视图(默认白色)遮盖了设置的颜色
self.navigationController?.navigationBar.backgroundColor = UIColor.kcRed
// ② 半透明关闭,此种方式可以设置导航栏颜色,展开图层树 UINavigationBar ->background 视图 -> imageView,发现 imageView 颜色变化(箭头代表内部子视图)
self.navigationController?.navigationBar.setBackgroundImage(UIImage(color:UIColor.kcRed), for: .default)
// ③ 半透明关闭,此种方式可以设置导航栏颜色。展开图层树发现是设置 UINavigationBar 的子视图 background 的颜色, 但根据 API 语义(barTintColor)明显不是设置背景专属,可能会影响内部子视图颜色,因此一般不建议采用此种方法来设置背景色
self.navigationController?.navigationBar.barTintColor = UIColor.kcRed
隐藏导航栏下线
// 展开图层树发现黑线是一个高度为 0.33 的 imageView(iphoneX 显示),图层树 UINavigationBar ->background 视图 -> imageView,颜色为透明度 0.3 的黑色,
self.navigationBar.shadowImage = UIImage()
3. 方案讨论
方案一
方案说明:用系统导航栏,且导航栏颜色控制仅仅在每个视图控制器 viewWillAppear 中进行配置,透明导航栏也可以使用颜色控制。当然也可根据需要部分页面隐藏导航栏。
存在问题:此方案过于简单,页面过渡和手势滑动时导航栏颜色效果变化突兀。
样例:参见 KenshinCui 博客的名为原始方式的方案(见其博客代码示例的 Demo1)。
关注点:不满足关注点 3,页面过渡导航栏背景颜色变化突兀。
方案二
方案说明:隐藏系统导航栏,切换不同颜色的导航条则只需要隐藏用这个方法隐藏导航条然后自定义一个 UINavigationBar 增加到导航条的位置(添加一个假的导航条)。不过这种方式的由于隐藏了导航条,那么侧滑返回手势也会消失。透明导航条直接隐藏导航条。
存在问题:①需要自己添加 UINavigationBar。②由于隐藏了系统的导航栏,造成侧滑手势丢失。解决方式是重新设置当前控制器的 interactivePopGestureRecognizer.delegate=self,但是多次 push、pop 会出现界面错乱操作失效的问题(解决方式就是在适当的时候禁用侧滑或者禁止手势 shouldReceiveTouch)。
样例:参见 KenshinCui 博客的方案 1(见其博客代码示例的 Demo2)。
关注点:由于需要添 UINavigationBar 所以不满足关注点 1;此方案导航栏内容和背景随视图渐进平移,背景不突兀,不满足关注点 2,但满足关注点 3;对于关注点 4 需要控制好手势的响应。此方案实现起来复杂,并且导航栏原生的特殊效果没有(自适应调整滚动视图、iOS 11 的大标题特效等),但此方案并没有突兀点,不影响需求的话可以采用。
方案三
方案说明:系统导航栏透明,自定义导航栏背景视图,将系统原有导航栏的背景设置为透明色,同时在每个 ViewController 上添加一个 View 或者 NavigationBar 来充当我们实际看到的导航栏,每个 ViewController 同样只需要关心自身的样式即可。当然也可根据需要部分页面隐藏导航栏。
存在问题:基本上满足需求,但和系统原生比较起来,需要自己实现半透明效果,另外可在转场过程中通过 self.transitionCoordinator?.animateAlongsideTransition 设置 navigationBar 透明度。
样例:参见 KenshinCui 博客的方案 2(见其博客代码示例的 Demo3)。
关注点:基本满足所列 4 个关注点。
方案四
方案说明:隐藏导航栏,每个页面包含一个 NavigationController , 每个页面有 2 个 ViewController 和一个 NavigationController,一个 ViewController 交给所属导航管理页面跳转,且其子视图为 NavigationController(寄宿到另一个 ViewController)。我们具体细节内容布局在导航内层那个 ViewController。
存在问题:视图结构复杂,过渡时导航内容的没动画,手势处理需谨慎(面临两个导航)。
样例:网传网易云音乐是这样的
关注点:看起来和方案二相似,更好的满足关注点 1。不满足关注点 2。满足关注点 3,如果手势处理好可满足关注点 4。相对每个自身页面而言,导航栏的原生特殊效果可以通过内层 NavigationController 达到。
方案五
方案说明:使用系统导航栏,页面过渡添加 Fake Bar 在转场的过程中隐藏原有的导航栏并添加假的 NavigationBar,当转场结束后删除假的 NavigationBar 并恢复原有的导航栏,这一过程可以通过 Swizzle 的方式完成,而每个 ViewController 只需要关心自身的样式即可。当然也可根据需要部分页面隐藏导航栏。
存在问题:但在解决 Bug 的时候,Swizzle 这种方式无疑会增加解决问题的时间成本和学习成本。
样例:美团
关注点:不满足关注点 2,其它满足。
4. 推荐方案
优先推荐方案 3,简单易用;方案 3 为避免出乱子,需要良好的团队代码规范和完善的技术文档来做辅助。
如果旧项目并且历史问题较多采用方案 5。
方案 2 和方案 4 满足需求的情况下也可选用,但这两个方案较复杂。