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

涉及到刷新,ScrollView嵌套,导航栏渐变,功能比较复杂,时间还比较赶,这就比较蛋疼了,所以在 Github 上找了个比较成熟的组件库来使用,效果不错。乘着项目结束之后呢,学习一下它的实现原理。Fork了工程,并在里面做了代码注释。
框架构成
- View
- Category
- Configration
- YNPageViewController
View
- YNPageTableView
UITableview的子类,内部遵循 <UIGestureRecognizerDelegate>
并实现
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer{
return YES; // 是否支持同时执行多种手势。 默认是不支持的
}
题外话:关于 UIGestureRecognizerDelegate
- YNPageScrollView
UIScrollView的子类,内部遵循 <UIGestureRecognizerDelegate>
同 YNPageTableView 实现 多手势 的处理 。 判断用户是否是 想要返回上一页的手势操作!!!
- YNPageHeadScrollView
YNPageScrollView的子类,遵循了 UIScrollViewDelegate 代理。
实现了 scrollViewDidScroll 方法,内部将偏移量设置为 (0,0)。
- YNPageCollectionView
UICollectionView 子类, 支持多手势执行
- YNPageScrollMenuView
UIView 子类, 菜单栏视图控件 ,支持文字长度自适应,滚动展示,多种样式
YNPageScrollMenuViewDelegate 协议:点击单个Item的协议方法,点击Add按钮的协议方法
由于前段时间另外一个项目开发中也涉及到了菜单栏组件,功能差不多,但是由于样式过于定制化,就自己动手封装实现了一下。
然后在看到这个类之后呢,正好将自己封装的和它封装的两者做了一下对比。虽然控件比较简单,
但是正是由于简单控件的封装,更能体现基本功的差距。
真是惭愧,自感差距在于以下:
1、它用UIButton做为Item ,而我用的UILabel, UIButton自带支持图片+文字的表示。
2、标题宽度的计算,使用 sizeToFit 计算文字宽度,而我使用 boundingRectWithSize。
3、Frame的计算更节省时间和内存,一次遍历布局完成。
4、提供了很多自定义样式的属性。
Category
- UIScrollView+YNPageExtend
a 运用 关联 增加是否观察scrollview滚动的属性 和 bolck 属性
b 替换系统的 scrollview 的 _notifyDidScroll 和 _scrollViewWillBeginDragging 代理回调方法 为自己的yn_代理方法
- UIView+YNPageExtend
添加x,y,width,height,bottom等快速frame布局所需属性
- 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 :
初始化:
- View 上 add 了 头部视图 HeadView 以及 菜单栏 Menu 和 pageScrollView , pageScrollView 存放 子视图控制器的View上的scrollview子类(以TableView为例)。
- 设置 第一个展示的TableView的时候,它 将 HeadView 以及 菜单栏 Menu 作为了这个 TableView(会给它设置内容偏移 给 HeadView和Menu展示,这也是为什么我们上下滑动它们俩的时候,TableView跟着滑动了) 的 子视图,给用户一种它们是UITableViewHeaderView的错觉
核心处理:
左右滑动 pageScrollView 的时候:
需要保证 HeadView 和 Menu 不跟着滚动 ,那么 怎么处理呢?
- 首先将 HeadView 和 Menu 从 TableView 里取出来放到 View上,这样就不会左右跟着滚动。
- 展示要展示的子视图控制器的View上的TableView (这边注意,它不是一开始就全部就将子视图控制器add了,而是要展示的时候再添加的,并在这时加入到缓存容器字典中)。
左右滑动 pageScrollView 结束减速 时 :
- 将 头部视图和菜单栏 重新 挪回到 子视图的 scrollview 上。
- 移除上一个展示的子视图控制器(从展示容器字典中移除,但任保留在缓存容器中)。
上下滑动TableView时
- 处理Menu临界悬浮
YNPageStyleSuspensionTopPause
初始化:
- 在 View 和 其他控件 之间 插入了 一个 bgScrollView (YNPageScrollView对象)
- bgScrollView 放置 HeadView 和 Menu 和 pageScrollView , 此时就不需要 像其他模式 一样 将 HeadView 和 Menu 反复替换父视图了。
核心处理:
由于 bgScrollView(YNPageScrollView) 支持手势共存,那么 在 上下滑动 TableView 的时候需要处理如下:
- 将要开始拖拽时,记录当前 bgScrollView 的 Y 偏移量 和 当前的TableView 的 Y 偏移量
- 滚动时计算Menu悬浮顶部偏移量
其他学习到的知识点
- NSAssert的使用
NSAssert是一个预处理宏, 他的主要作用就是可以让开发者比较便捷的捕获一个错误, 让程序崩溃, 同时报出错误提示。
NSAssert是预处理指令使用过多会影响程序运行,所以加 DEBUG 的版本限制。
#if DEBUG
NSAssert(x, @"错误提示”);
#endif
// 当你的程序在运行到这个宏时, 如果变量x条件不成立时 , 此时程序就会崩溃, 并且抛出一个异常, 异常提示就是你后面写的提示。
- UNAVAILABLE_ATTRIBUTE
告知方法失效
- sizeToFit 与 sizeThatFit
sizeThatFits: 会计算出最优的 size 但是不会改变 自己的 size
sizeToFit: 会计算出最优的 size 而且会改变自己的 size。那么两者的联系是什么呢?
实际上,当调用 sizeToFit 后会调用 sizeThatFits 方法来计算 UIView 的 bounds.size 然后改变 frame.size。