[iOS]UINavigationController 全屏 pop 之为每个控制器自定义 UINavigationBar

2,361 阅读7分钟

声明:我为这个框架写了四篇文章:

第一篇:[iOS]UINavigationController全屏pop之为每个控制器自定义UINavigationBar

第二篇:[iOS]UINavigationController全屏pop之为每个控制器添加底部联动视图

第三篇:[iOS]UINavigationController全屏pop之为控制器添加左滑push

第四篇:[iOS]调和 pop 手势导致 AVPlayer 播放卡顿

框架特性

✅ 全屏 pop 手势支持

✅ 全屏 push 到绑定的控制器支持

✅ 为每个控制器定制 UINavigationBar 支持(包括设置颜色和透明度等)

✅ 为每个控制器添加底部联动视图支持

✅ 自定义 pop 手势范围支持(从屏幕最左侧开始计算宽度)

✅ 为单个控制器关闭 pop 手势支持

✅ 为所有控制器关闭 pop 手势支持

❤️ 当当前控制器使用 AVPlayer 播放视频的时候, 使用自定义的 pop 动画以保证 AVPlayer 流畅播放.

说明:有很多同学,包括你能看到,留言栏里也有同学说原作者不推荐使用这个框架。然后就来问我,是否能在自己的项目里引入这个框架。从技术的角度来说,我个人的想法是,你要尝试自己去衡量。可能很多同学都看到原作者不推荐,但是却没注意不推荐的原因:“细节问题会比这个完善很多”。我不解释,只把原因单独贴出来了。补充一点,凡是我在自己的项目中使用碰到的细节问题,都已经修复好了。
有一个同学担心性能问题,他的担心是有依据的,因为毕竟经过包装处理以后,以前每次只需要压入一个控制器到栈,现在是每次压入三个。其实碰到这种问题,我是尝试去做循环利用的,但是由于这里问题的特殊性(为什么特殊?),我至今仍在思考合适的方法能够达成循环利用的效果。为了解除这部分同学的疑问,你可以使用以下网易云音乐,他们是这种模式的缔造者,同时通过Reveal观察,没有迹象表明他们实现了这部分的循环利用,这就是我对性能问题的交代。如果有一天我找到了一个合适的方式来做循环利用我肯定还会写一篇文章来说明这个过程的。

As we all know,Apple 提供的导航条是用来管理窗口控制器的结构的,某个 UINavigationController 执行 Push 操作后,该导航控制器管理的 controllers 共用一个导航条,如果对该导航条进行自定义,那么各个界面的导航条都会变成自定义以后的样式。但是,实际开发中,我们可能需要为不同的控制器定制不同的导航条,就像我 demo 里写的一样.

关于demo,总结一下

  • 这个 demo 的结构是这样的,UITabBarController 作为根控制器管理着一个导航控制器和一个 UIViewController
  • 导航控制器下面有一个 UITableViewController,为了完整的显示图片,该 UITableViewController 的导航栏要求是透明的
  • push 到下一个控制器的时候,要求导航栏正常显示
  • 再 push 下一个界面,要求导航栏的颜色是红色
  • 最后,最重要的一点,要求全屏右滑返回的时候,导航条跟随自己的控制器滑动

01、原作 JNTian的思路?

用 Reveal 观察了几个常用 App,发现了这种效果的实现大致分3种:

第一种是使用自定义 navigationBar.淘宝,网易新闻,达令等使用的是这种. 第二种是用截图的办法,在 push 到下一个页面时,截取屏幕,在使用 edgePanpop 时看到的就是背后的截图,也能实现这种效果.京东,天猫等使用的是这种. 第三种是使用了一种比较特别,比较巧妙的办法实现的,也就是网易云音乐的实现方法,后面会分析一下这种实现.

作者用 Reveal 工具考察了实现这一效果的三种已知的方式,在对比这三个实现方式各自的优缺点以后,采用了第三种方式来实现。

02、究竟怎么实现的?

要探究实现,先要讲清楚一个原则:

  • 在苹果的规则里 UINavigationControler 嵌套 UINavigationControler 的方式是不被允许的,也不能执行压入栈 push 和出栈 pop 动作

跟我看三张图:

窗口根结构图.png

还记得开头的 demo 吗?demo 的结构就是这样一个窗口结构图:

  • 窗口的根控制器是 TabBarController(上图紫色的 view
  • TabBarController 每一个分支子控制器,用一个根导航控制器作为管理者(上图小一点的黑色 View
  • 使用 setNavigationBarHidden:将根导航控制器的导航栏彻底移除

pop和push结构图.png

  • 每当用户设置根导航控制器的 rootViewControllerpush 入栈的时候,要先将传进来的控制器(上图蓝色的 View)先包装一层导航控制器(上图白色的View
  • 上面我们说的第二个原则,UINavigationControler 嵌套 UINavigationControler 的方式是不被允许的,也不能执行压入栈 push 和出栈 pop 动作
  • 所以我们要在导航控制器(上图白色的 View)外层再包装一层 UIViewController
  • 经过以上的包装以后,用户传进来的 Controller 就拥有了一个自己专有的导航控制器,我们可以在对应的控制器的 .m 文件里对该导航控制器做任何自定义(设置透明度和颜色以及渐变)
  • 而且同时也实现了,最重要的一点,要求全屏右滑返回的时候,导航条跟随自己的控制器滑动

窗口结构图.png

上面两张图,我们分析了怎么设定根视图的结构,以及怎么包装用户传进来的控制器。 有了以上的基础以后,我们就可以进行入栈和出栈的操作了。 注意:

  • TabBarController 每一个分支子控制器的 pushpop 操作都是由根导航控制器负责
  • poppush 操作的对象都是我们包装过后的 WarpViewController,这样就不会有导航控制器 pushpop 导航控制器的冲突了
  • 而且,毫无疑问,也必须设定一个开关来支持全屏右滑返回。

03、代码实现?

代码实现见 GitHub。或者也可以参见原作者的 Github地址。我只是在原作的基础上加了注释,并没有修改代码。 如果你对demo中图片在tableView滚动时的视差效果感兴趣,可以参见我以前的文章 仿Airbnb的tableView头部视图层叠效果。 最后,谢谢Github开源作者: JNTian,大写的感谢。

04、更新

  • 2016.08.02: 有朋友在QQ上联系我说,他在实际开发中有需要在某个界面暂时关闭右滑手势的需求。所以,加入暂时关闭右滑手势开关,方便在某些情况下需要暂时关闭手势。具体更新见我的GitHub_Demo

  • 2016.08.04: 上个版本使用类工厂方法,在类工厂方法里操作了用户传进来的控制器的View,所以会造成在应用启动的时候TabBarVC的子控制器一起加载,有性能问题.这次提交修复了这个问题, TabBarVC的子控制器都能遵循懒加载的原则.谢谢:袁小荣同学(Github)的提醒.

  • 2016.08.08: 这个框架有了较大的更新。具体包括给现有的Pop手势添加暂时关闭开关,以及自定义响应手势范围。在现有基础上,框架添加了底部联动视图,具体实现以及思路,请前往我的文章:1行代码为每个Controller自定义“TabBar”

  • 2016.09.13 添加了直接拿到根导航控制器的接口,方便使用popToViewController功能。具体见demo第三个控制器的Pop。

05、注意

注意: tabBar 的 translucent 默认为 YES, 使用 JPNavigationCotroller 不能修改 tabBar 的透明属性. 这是因为 Xcode 9 以后, 苹果对导航控制器内部做了一些修改, 一旦将 tabBar 设为不透明, 当前架构下的 UI 就会错乱, 设置 tabBar 的 backgroundImage 为不透明图片, 或者设置 backgroundColor 为不透明的颜色值也是一样的会出错.

NewPan 的文章集合

下面这个链接是我所有文章的一个集合目录。这些文章凡是涉及实现的,每篇文章中都有 Github 地址,Github 上都有源码。

NewPan 的文章集合索引

如果你有问题,除了在文章最后留言,还可以在微博 @盼盼_HKbuy 上给我留言,以及访问我的 Github