声明:
- 最后更新时间:2019年3月13日
- 为保证时效性,持续更新地址为:iOS页面转场导航栏样式方案梳理
1.背景
在iOS开发中每个页面都有可能被个性化设计,但如果页面是以push方式进行管理,那么多个视图控制器共享一个导航栏,导航栏的适配显示就是一个问题。因此需基于系统导航进一步调整和修改才能满足需求。本文参考下面两篇博客进行分析梳理。
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满足需求的情况下也可选用,但这两个方案较复杂。