源码iOS开发专题汇集iOS进阶

APP常见的滑动导航实现:TYPagerController源码

2016-09-28  本文已影响2750人  ZZZEoEv

写在前面

滑动视图导航是APP中经常用到的一种视图,自己之前也造过相关的轮子。
偶然间发现了TYPagerController这个第三方库,其加入了NSCache对ScrollView的性能进行了优化,很值得学习,所以便有了此文来记录,方便日后查看。
在其首页上有具体的效果演示,在这里我就不多做介绍了。

看完本系列文章,我相信大家都能写一个自己的导航控制了。

一、总体介绍

这就是一个常见的滑动导航控制器:


1.1 滑动导航视图控制器的总体构成

主要分两个部分:

滑动导航控制器的总体流程也很简单:

  1. 将ViewController.view加入到下方的ScrollView中
  2. 根据数据源Titles对上方TabBar中CollectionCell上的Label赋值
  3. 处理下方ScorllView与上方TabBar之间的协同问题

这里面有几个坑:
流程1会存在比较严重的性能问题;
流程3需要规划在相应的处理方法中做正确的事情。

1.2 TYPagerController整体介绍

TYPagerController针对上文提到的流程1、3存在的问题做出了相应的改进,也是我们需要重点关注的地方:

其整体是自上而下的继承关系:



其中:

我们一般直接用TYTabButtonPagerController就可以了,如果有自定义cell的需求,可以使用TYTabPagerController注册自己的cell。

二、TYPagerController基类介绍

实现一个滑动导航,如果不加头部的切换TabBar,思路跟初始化一般的ScrollView是一样:

  1. 初始化ScrollView
  2. 根据需要显示的页面数量和宽度(一般是一整个屏幕宽度),确定ContentView的Frame

TYPagerController基类,在此基础上,加入了缓存、代理和总体逻辑流程控制。

其中,缓存部分就是很简单的使用了NSCache,代理我们简单介绍一下,主要是看一下作者对滑动试图导航的流程控制是怎样的。

三、代理协议

协议部分,跟常用的tableView一个思路,定义数据源和代理:

四、TYPagerController基类流程分析

本章共计三部分,重点在2、3部分:

  1. init
  2. LifeCycle
  3. 滑动逻辑处理

原作者对部分子方法和参数的命名容易产生歧义,所以我对其进行了一部分重构,这样大家读起来会顺畅一些。

初始化布局阶段,整体流程是这样的:

4.1 init

初始化部分很简单,就是对一些必要的数据进行赋值:

4.2 Life Cycle

生命周期这部分,主要是初始化ScrollView,根据dataSource进行布局。

4.2.1 updateContentViewIfNeeded:

4.2.2 updateContentView:

小小结

至此呢,作者做的事情跟传统使用ScrollView的思路是一模一样的(初始化Scrollview,配置contentSize),只不过中间加入了对statusBar高度适应方面的判断。

4.2.3 layoutSubViewsInContentView

讨论

scrollView的contentSize确定下来以后,就需要添加subviews了。
这时候,最简单的做法就是,一次性添加所有subviews到scrollView上。
但是这样会带来很严重的内存占用(想象一下你有30个tableview,每个tableview有1000+的cell)。

这时候就需要更好的解决办法,我们只添加需要显示的subviews就是了:

  1. 确定一个需要显示的index range(滑动过程中,需要显示的VC将不止一个,所以需要一个range)
  2. 移除range外的VC,只显示range范围内的VC

这样做的好处,自然是节省内存;
但带来的问题则是,需要反复的init、dealloc我们的ViewController,带来不必要的性能损失。

解决办法呢,加入缓存呗。
使用NSCache也好,自己创建Dict也好,能避免我们的ViewController被销毁就行。
这样修改之后的大体流程是这样的:

  1. 确定一个需要显示的index range
  2. 根据index从visibleVCs、Cache或者DataSource中获取VC
  3. 根据不同情况,将获得到的VC加入到childVC、visibleVC或者Cache中
  4. 如果visibleVCs中有range之外的VC,则将其及其视图从visibleVCs、childVC和视图层级中移除

加入缓存之后,我们的ViewController不会被反复的init、alloc,同时也会显著降低内存占用(ViewController的View没有加入视图层级中)。

我们来看一下具体的代码:

其中,内联函数可能有的童鞋不了解,其可以理解为宏定义,只不过内联函数在宏定义的基础上,加入了返回值校验等等一般函数具有的功能的同时,可以避免函数入栈操作,从而节省开支。


从上面的代码可以看出,该函数主要是根据offset和width计算出visibleRange。
后面ScrollView的代理方法中,会在滑动过程中实时的调用这个内联函数,计算出range数据,然后再根据range进行添加删除VC的操作。

addControllersInVisibleRange

removeControllersOutOfVisibleRange

至此,init及life Cycle的分析已经完成了,接下来是运行时的交互部分了。

4.3 滑动逻辑处理

基类中函数调用顺序是这个样子的:


很容易理解:

  1. 根据目前offset确定起始、目标index和滑动比例(progress)
  2. 根据index获取顶部TabBar对应的cell(下一篇的内容)
  3. 根据cell的frame和滑动比做动画(下一篇的内容)
  4. 重新布局Subviews(增加可见范围内Index对应的View,移除可见范围外的View)

scrollViewDidScroll

细心的朋友可能发现了,这里进行了两次index的计算,至于原因嘛,我们去看看具体实现代码好了。

configurePagerIndexByScrollProgress

作用:计算fromIndex、toIndex和滑动比
实现:对offsetX/width分别取整数部分(index)和小数部分(滑动比)
说明:方法名中对progress的计算就是取offsetX/width的小数部分

计算progress的方法,是实时的。
也就是说,只要offset发生了变化,该方法就会调用,子类处理progress的方法也会被调用。
所以,适合用来处理需要实时反馈的事件,比如控制underLineView的Frame,调整Label的transform、color等。

configurePagerIndex

作用:计算fromIndex、toIndex,在index发生改变的时候才会触发处理方法

至此,就很明了了。
configurePagerIndex虽然也是计算index,但是有一个阈值和比较的步骤存在,这样只有当offsetX的改变超过阈值并且index确实更改了之后,才会调用子类和代理的处理方法。
比如,修改collectionView的offset使currentIndex居中这样只需要在currentIndex改变的情况下处理的事情,就可以在该方法中调用。

五、总结

回头看一下我们开篇提到的两个『坑』,TYPagerController是如何处理的:

本篇已经将优化部分分析完了;
接下来的一篇,会着重分析子类中transition方法的实现。

上一篇下一篇

猜你喜欢

热点阅读