ScrollView、TableView、CollectionView 联动一种实现思路
ScrollView、TableView、CollectionView 联动一种实现思路
首先看看效果:
- 带悬停View头部的类型,可以上下拉刷新
2.不带悬停View头部的类型,可以上下拉刷新
(PS.图床上传GIF有问题,经常出现卡死问题,直接运行DEMO比较直观)
→ Demo地址
联动思路实现
核心思路
大部分处理方案
由于联动是在最底部的 ScrollView
上面添加一个 TableVIew
或者 CollectionView
之类的, 最大难点就是 手势
处理 . 嵌套 ScrollView
之后,需要一个条件来控制某个具体时刻那个 ScrollView
响应滑动事件。
通常,我们会在 ScrollView
代理方法里面
1 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { |
例如,我们会实现这样一个逻辑,最底部有一个 mainScrollView
, mainScrollView
上有一个 SubScrollView
, 这样就类似一个双 ScrollView
嵌套场景了
1 | //写个伪代码 |
代理方法里面 scrollViewDidScroll
中进行判断,当 mainScrollView
划出了 maxOffsetY
,停止响应滚动,继续滑动时候,就是 subScrollView
响应滚动了
但是,在实际操作上有个很大问题,使用者并不是总能滑动到 maxOffsetY
地方,停下手,进行下一次滑动,如果滑动大于 maxOffsetY
地方, mainScrollView
就会滚动到 maxOffsetY
之后,停止响应,是因为这是一次手势, subScrollView
也不会响应,使用者就有中断感觉,需要第二次滑动才能继续滚动
改变一下思路
由于有上面的不足,我们就改变一下思路.
大家都知道,每个 ScrollView
都带有一个 panGestureRecognizer
的手势,如果可以控制这个手势,就理论上能优化了,那么我们就有:
1 | mainScrollView.panGestureRecognizer.delegate = self; |
结果App 运行后报错:
‘UIScrollView’s built-in pan gesture recognizer must have its scroll view as its delegate’
解决的思路是我们自己添加一个联动的 pan 手势
UIGestureRecognizerDelegate里面有一个委托方法:
1 | - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer { |
只需要将其返回YES即可同时执行多种手势;如果手势之间或手势对应的视图有冲突或者有不同的需求,可以在这个方法里面添加一些相应的判断,例如一个uiview里面有3个手势分别是移动、缩放、旋转,而我只需要同时执行缩放和旋转的话:
1 | // called when the recognition of one of gestureRecognizer or otherGestureRecognizer would be blocked by the other |
一句话总结就是此方法返回YES时,手势事件会一直往下传递,不论当前层次是否对该事件进行响应。
那么,我们可以这样做,在 mainScrollView
实现这个代理,让手势传下去,给 subScrollView
回到Demo:
此Demo的 ScrollView
样子大概是这样:
如图, 红色框柱区域为 mainScrollView
, 蓝色框柱地方为 HeaderView
, 紫色框柱的地方是 subScrollView
(CollectionView) ;
那么,如果需要悬停(就是红色 headerView
那部分,在 mainScrollView
上滑到最上面时候,卡在最上面, subScrollView
继续滑动)
实现思路类似上面所说的,在ScrollView代理中方法里面做文章
1 | - (void)scrollViewDidScroll:(UIScrollView *)scrollView { |
UI布局
首先, subScrollView
是一个 Controller
里面的 CollectionView
, mainScrollView
的Controller最底部那个View, 上面添加了一个 headerView(UIView可悬停)
, 再添加一个可以左右切换的 segmentScrollView(请联系源码看)
, subScrollView
就是添加在这个可以左右切换的ScrollView
上,总体来说,mainScrollView
的 ContentSize
就是 HeaderView的高度
+ subScrollView里面CollectionView的高度
.
滑动传递
滑动到的时候,因为上面设置了 mainScrollView
的手势方法,可以响应多手势. 在拖着 subScrollView
,即 CollectionView
的那个紫色区域时候, 底部的父视图的 mainScrollView
也会收到响应,两个 ScrollView
都会进入各自 Controller
的 (void)scrollViewDidScroll:(UIScrollView *)scrollView
方法里面,我们只要在各自方法里面写相应的逻辑判断,则会达到一个联动效果.
滑动逻辑
(具体详情可查看源码,这里说一个大概)
mainScrollView
:
didScroll
代理里面
1 |
|
先说下这个逻辑, isTopIsCanNotMoveTabViewPre
与 isTopIsCanNotMoveTabView
是一个全局变量,记录了当前状态与前一个状态是否能滑动状态,每次滑动时候,将根据是否达到了极限值而改变布尔类型
1 | offsetY >= self.scrollMinOffsetY + _headerViewTotalHeight |
因为一开始,mainScrollView
的offset是在(0,0),向上拖动时候,offsetY增大, 当小于极限值时候 (如果是需要悬停状态,那么极限值就是 头部HeaderView高度 - 需要悬停高度) , 滚动的是 mainScrollView
, subScrollView
的 contentOffset
将会保持 (0,0) , 同理,如果大于极限值, mainScrollView
的 contentOffset
也将会保持一个固定值,那么滑动的是 subScrollView
, 两者之间将会用通知传递状态信息
注意
Demo中通知名使用的是唯一通知,好处就是,如果多个 Controller
也用到这种结构,在 NavigationController
视图堆栈里面还没用清空销毁的 Controller
也会接收到另外 Controller
通知,也会触发到 didScroll
方法里面的逻辑,所以,稳妥方法是不同 模块的Controller用唯一通知名
滑动到极限位置
滑动到极限位置时候,HeaderView固定在极限位置, 即 MainScrollView
滑动到极限位置, 然后在 didScroll
代理方法里面,发通知给 SubScrollView
,让 SubScrollView
可以滑动
1 | if (self.isTopIsCanNotMoveTabView != self.isTopIsCanNotMoveTabViewPre) { |
逻辑中,根据 canScroll
判断 sub
和main
是否能滑动
上下拉刷新回调
这套实现思路,能做到上下啦刷新,上拉刷新好说,就是在 mainScrollView
上面添加刷新控件,例如 MJReresh
, 下拉刷新的话,需要再 subScrollView
上添加,因为,在 mainScrollView
固定不滚动时候,滚动的响应视图是 subScrollView
, 所以 , 只能通过回调告诉 mainScrollView
做相应操作,当然,如果你项目逻辑只需在 subScrollView
里面完成就行的话,就不需要回调,一切视乎项目要求
最后
说下上面实现可能在项目中一些功能上,需求上不足
- 如果数据量太少,例如数据量不足一屏幕,
CollectionView
或者Tableview
创建时候,是基于一个全屏幕的,所以在滚动到极限位置,开始滚动subScrollView
时候,下面会有很大一片空白位置, 暂时知道的微博
也会存在这个问题,如果你们设计没有太大要求就没什么问题。 那么对于这种,解决方法也不是没有,CollectionView
通过
1 |
|
这个方法,completion
时候,能获得 collectionView
正真的 contentsize
高度,那么可以判断是否少于屏幕高度尺寸,再直接设置 mainScrollView
与 subScrollView
的 contentSize
来不给他滚动太大距离即可, TableView
就比较麻烦,需要提早缓存 高度,请求数据完成后,根据数据数量与每个缓存高度,计算是否少于屏幕尺寸也做响应操作。在左右切换 ScrollView
时候,也要动态即时改变,这个是比较麻烦的坑
另外如果有更好的解决方法,欢迎留言~谢谢
Reference:
ScrollView、TableView、CollectionView 联动一种实现思路