YNPageViewController 源码阅读

3,291 阅读5分钟

前言

之前在开发项目中呢,UI同学设计了比较复杂的嵌套页面布局,Like This:

涉及到刷新,ScrollView嵌套,导航栏渐变,功能比较复杂,时间还比较赶,这就比较蛋疼了,所以在 Github 上找了个比较成熟的组件库来使用,效果不错。乘着项目结束之后呢,学习一下它的实现原理。Fork了工程,并在里面做了代码注释。

框架构成

  • View
  • Category
  • Configration
  • YNPageViewController

View

  1. YNPageTableView
   UITableview的子类,内部遵循 <UIGestureRecognizerDelegate>
并实现
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
    return YES; // 是否支持同时执行多种手势。 默认是不支持的
}

题外话:关于 UIGestureRecognizerDelegate

  1. YNPageScrollView
  UIScrollView的子类,内部遵循 <UIGestureRecognizerDelegate>
同 YNPageTableView 实现 多手势 的处理 。 判断用户是否是 想要返回上一页的手势操作!!!
  1. YNPageHeadScrollView
  YNPageScrollView的子类,遵循了 UIScrollViewDelegate 代理。 
实现了 scrollViewDidScroll 方法,内部将偏移量设置为 (0,0)。
  1. YNPageCollectionView
  UICollectionView 子类, 支持多手势执行
  1. YNPageScrollMenuView
  UIView 子类, 菜单栏视图控件 ,支持文字长度自适应,滚动展示,多种样式
  YNPageScrollMenuViewDelegate 协议:点击单个Item的协议方法,点击Add按钮的协议方法
  
  由于前段时间另外一个项目开发中也涉及到了菜单栏组件,功能差不多,但是由于样式过于定制化,就自己动手封装实现了一下。
  然后在看到这个类之后呢,正好将自己封装的和它封装的两者做了一下对比。虽然控件比较简单,
  但是正是由于简单控件的封装,更能体现基本功的差距。
  真是惭愧,自感差距在于以下:
  1、它用UIButton做为Item ,而我用的UILabel, UIButton自带支持图片+文字的表示。
  2、标题宽度的计算,使用 sizeToFit 计算文字宽度,而我使用 boundingRectWithSize。
  3、Frame的计算更节省时间和内存,一次遍历布局完成。
  4、提供了很多自定义样式的属性。

Category

  1. UIScrollView+YNPageExtend
a 运用 关联 增加是否观察scrollview滚动的属性 和 bolck 属性
b 替换系统的 scrollview 的 _notifyDidScroll 和 _scrollViewWillBeginDragging 代理回调方法 为自己的yn_代理方法
  1. UIView+YNPageExtend
添加x,y,width,height,bottom等快速frame布局所需属性
  1. UIViewController+YNPageExtend
增加了一些快速取得 YNPageViewController 类库中常见对象的便捷方法,比如获取 配置项(YNPageConfigration),获取全部的子视图控制器(controllersM)等,

Configration 配置项

提供了很丰富的属性给开发者使用,进行定制化操作。

菜单栏 YNPageScrollMenuView 的 样式配置
视图控制器YNPageViewController 的 样式配置

YNPageViewController (核心)

这也是本组件的核心,也花费了好长时间去阅读和理解的,不能说代码全部阅读理解,但基本也算理解完成。😂 。 首先它提供了五种样式:

  • YNPageStyleTop: MenuView在顶部
  • YNPageStyleNavigation: MenuView在系统导航条
  • YNPageStyleSuspensionTop: MenuView悬浮,刷新控件在HeaderView顶部
  • YNPageStyleSuspensionCenter: MenuView悬浮,刷新控件在HeaderView底部
  • YNPageStyleSuspensionTopPause: MenuView悬浮,刷新控件在HeaderView顶部 停顿 类似QQ联系人页面

YNPageStyleTop ,YNPageStyleSuspensionTop , YNPageStyleSuspensionCenter :

初始化:

  1. View 上 add 了 头部视图 HeadView 以及 菜单栏 Menu 和 pageScrollView , pageScrollView 存放 子视图控制器的View上的scrollview子类(以TableView为例)。
  2. 设置 第一个展示的TableView的时候,它 将 HeadView 以及 菜单栏 Menu 作为了这个 TableView(会给它设置内容偏移 给 HeadView和Menu展示,这也是为什么我们上下滑动它们俩的时候,TableView跟着滑动了) 的 子视图,给用户一种它们是UITableViewHeaderView的错觉

核心处理:

左右滑动 pageScrollView 的时候:

需要保证 HeadView 和 Menu 不跟着滚动 ,那么 怎么处理呢?

  1. 首先将 HeadView 和 Menu 从 TableView 里取出来放到 View上,这样就不会左右跟着滚动。
  2. 展示要展示的子视图控制器的View上的TableView (这边注意,它不是一开始就全部就将子视图控制器add了,而是要展示的时候再添加的,并在这时加入到缓存容器字典中)。
左右滑动 pageScrollView 结束减速 时 :
  1. 将 头部视图和菜单栏 重新 挪回到 子视图的 scrollview 上。
  2. 移除上一个展示的子视图控制器(从展示容器字典中移除,但任保留在缓存容器中)。
上下滑动TableView时
  1. 处理Menu临界悬浮

YNPageStyleSuspensionTopPause

初始化:

  1. 在 View 和 其他控件 之间 插入了 一个 bgScrollView (YNPageScrollView对象)
  2. bgScrollView 放置 HeadView 和 Menu 和 pageScrollView , 此时就不需要 像其他模式 一样 将 HeadView 和 Menu 反复替换父视图了。

核心处理:

由于 bgScrollView(YNPageScrollView) 支持手势共存,那么 在 上下滑动 TableView 的时候需要处理如下:

  1. 将要开始拖拽时,记录当前 bgScrollView 的 Y 偏移量 和 当前的TableView 的 Y 偏移量
  2. 滚动时计算Menu悬浮顶部偏移量

其他学习到的知识点

  1. NSAssert的使用

NSAssert是一个预处理宏, 他的主要作用就是可以让开发者比较便捷的捕获一个错误, 让程序崩溃, 同时报出错误提示。

NSAssert是预处理指令使用过多会影响程序运行,所以加 DEBUG 的版本限制。
#if DEBUG
NSAssert(x, @"错误提示”);
#endif
// 当你的程序在运行到这个宏时, 如果变量x条件不成立时 , 此时程序就会崩溃, 并且抛出一个异常, 异常提示就是你后面写的提示。 
  1. UNAVAILABLE_ATTRIBUTE

告知方法失效

  1. sizeToFit 与 sizeThatFit

sizeThatFits: 会计算出最优的 size 但是不会改变 自己的 size

sizeToFit: 会计算出最优的 size 而且会改变自己的 size。那么两者的联系是什么呢?

实际上,当调用 sizeToFit 后会调用 sizeThatFits 方法来计算 UIView 的 bounds.size 然后改变 frame.size。