介绍一种 iOS 中实现完全自定义导航栏的思路

3,850 阅读4分钟
原文链接: soledad.me

TL;DR

源代码 GitHub 地址:github.com/caiyue1993/…

前言

灵感最初来源于 Unread 的 iOS 客户端。

当我第一次打开这个应用时,我就被它的 UI 深深吸引了:和大多数国内 app 使用泛滥的 TNV (TabBarController -> NavigationController -> ViewController) 架构不同, Unread 它没有使用 UITabBarController 也没有使用系统自带的 UINavigationController,界面没有任何无关内容,让用户沉浸在应用中,享受应用提供的内容信息流,这种体验简直完美。

它的交互也几乎都是通过手势操作:下拉刷新,右滑(屏幕任何位置)返回,另外左滑和上提同样会触发相应动作。在小屏幕上可能感受不到这种便利,但是在稍微大些的屏幕上如果非要通过点击左上角的返回键才能返回的话,那真的是一件十分痛苦的事情。

Unread 位于底部的导航栏也很有意思,我以为这也是一种很大的创新,它摈弃了系统自带的 navigation bar,将导航栏移到屏幕的最下方,交互同样丝般顺滑,让人赞不绝口。

所以,我就想模仿着实现这样一个导航栏。

思路

当时立马脑海里想到的有三种大致的思路:

第一种,尝试是否能直接更改系统 navigation bar 的位置,将其移至屏幕下方,并设置合理的大小和想要的外形。

第二种,自己实现一个 NavigationController:里面放置一个 UIScrollView,将栈上的 view controller 都放入其中,然后配置一个自定义导航栏,根据 UIScrollView 的 contentOffset 来实现过渡效果。

第三种,直接继承系统的 UINavigationController 并进行私人定制,满足我们的需要。

筛选最优解

针对第一种思路,翻看官方的 UINavigationController 的文档,里面写了这样一句话:

The navigation controller manages the creation, configuration, and display of the navigation bar and optional navigation toolbar. It is permissible to customize the navigation bar’s appearance-related properties but you must never change its frame, bounds, or alpha values directly.

因此抛弃思路一。

第二种思路,很明显实现这种思路工作量较大,我们既然有了系统提供的 UINavigationController,为什么不在它提供的接口上进行些许的 tweak ,进而满足我们的需求呢?(其实在 GitHub 上已经有人用第二种方法实现了,有兴趣的读者可以搜搜)

所以最终我选择了短平快的第三种思路:对系统自带导航控制器进行定制,实现需求。

相关知识

关于 UINavigationController:

一个 navigation controller 它自己会维护一个 viewControllers 的数组,另外,navigation controller 有一个属性 view ,该 view 中包含了 navigation bar,toolbar(可选),另外还有一个 content view。在视图的前进以及后退中,只有 content view 是一直在变的(等于 viewControllers 最上面的 view controller 的 view),而 navigation bar 以及 toolbar(可选) 只有内容会变(例如标题),而它们本身作为视图是不变的。

关于 UINavigationItem:

每个被压入 navigation stack 中的 view controller 都必须有 UINavigationItem ,用来展示在 NavigationBar 上面。而 navigation controller 会根据 stack 最上面的两个 view controller 生成 navigation bar 的内容。

关于 UINavigationBar, 看官方文档中的一句话:

A navigation bar is most commonly used within a navigation controller. The UINavigationController object creates, displays, and manages its associated navigation bar, and uses attributes of the view controllers you add to control the content displayed in the navigation bar.

另外,我们可以通过 UINavigationBarDelegate 获取当前需要管理的 UINavigationItem,然后即可获取 UINavigationItem 的 title 等信息,展示在 NavigationBar 上。

如何实现右滑返回时的过渡效果

首先,加上了两个 UILabel: currentTitleLabel 和 priorTitleLabel,分别用来记录当前 view controller 的标题以及前一个 view controller 的标题。
另外,由于 UINavigationController 有一个 UIGestureRecognizer 属性,可以用来追踪右滑的手势,从而可以拿到在当前视图上的偏移量,进而可以实现自定义的过渡效果。
还有,UINavigationBarDelegate 提供了四个代理方法 (shouldPush,didPush,shouldPop,didPop),可以通过这样的四个节点来配置 currentTitleLabel 和 priorTitleLabel 的始末节点时的状态。

目前的效果是这样的:

之后要做的

  • 实现更多的过渡效果
  • 找到更好的解决中途取消右滑返回的方案(现在采用的是一种 work around,具体可以看源代码 TiptoesNavController.swift 中的 handleTiptoesDisplay(sender:) 方法)

个人精力有限,希望可以发挥开源社区的力量吧,最后再贴一下源代码地址:github.com/caiyue1993/…

如果你单纯喜欢本项目的想法,可以 ⭑Star 支持;如果你对项目代码有更好的建议,可以直接提 issue 或者 pull request。谢谢:)

参考资料

关注我


本文原地址:soledad.me/2017/01/18/…

本文版权所有,如需转载,请告知原作者并注明出处