ScrollView嵌套scrollView,如何解决联动问题

背景介绍

APP中经常用到的一个场景,一个页面,可以 竖向 滑动,中间有个segment控件,支持 横向 滑动,横向滑动的scrollView上面又放了一个个可以竖向滑动的scrollView。

github地址:

https://github.com/oscarwuer/yhlinkagetableview

框架搭建

整个页面基于一个tableView,从上而下,分别是海报cell简介cellsectionHeaderView容器cell。所以一共两个section,前一个section里有两个cell,后一个section只放一个容器cell。

由于最下面的也有三个tableView,为了区分开来,主页上的我们称为 mainTableView ,下面那三个称为 listTableView

sectionHeaderView 上放的是 HMSegmentControl

容器cell是重点,上面放了个 UIScrollView ,横向摆放三个tableViewController的view。注意,是控制器的view。你也可以直接放三个tableView在scrollView上,然后你会发现你的cell里N多个if... else...

所以整体架构就是这样

原理实现

在介绍原理之前,有个函数需要认识下

1
2
3
4
5
6
7
/*
* 是否允许多个手势识别器共同识别,一个控件的手势识别后是否阻断手势识别继续向下传播,默认返回NO;如果为YES,响应者链上层对象触发手势识别后,如果下层对象也添加了手势并成功识别也会继续执行,否则上层对象识别后则不再继续传播
*/
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
{
return YES;
}

备注上解释的很清楚,第一个重点,我们需要写一个tableView基类,实现这个方法, mainTableView 继承自此基类tableView。

listTableView 就是普通的tableView即可。现在的效果是,滑动listTableView,mainTableView也会跟着滑动,所以现在需要做的是,当listTableView未滑动到顶的时候,禁止mainTableView的滑动。 核心就是在一定的时机,设置mainTableView的scrollEnabled为NO和YES 。此处为第二个重点

1
2
3
4
5
6
7
8
9
10
- (void)mmtdOptionalScrollViewDidScroll:(UIScrollView *)scrollView {
self.tableView.scrollEnabled = NO;
}

- (void)mmtdOptionalScrollViewDidEndDecelerating:(UIScrollView *)scrollView {
NSUInteger page = scrollView.contentOffset.x/[UIScreen mainScreen].bounds.size.width;
[self.sectionView.segmentControl setSelectedSegmentIndex:page animated:YES];

self.tableView.scrollEnabled = YES;
}

第三个重点,当listTableView滑动到顶时,需要发送 通知 告诉mainTableView,我已经到顶了,该你滑动了。其实就是改变两个bool值,因为在mainTableView的 scrollViewDidScroll 代理方法中,需要用来判断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
if (scrollView == self.tableView) {

CGFloat offset = 64;
if (iPhoneX) {
offset = 88;
}
CGFloat bottomCellOffset = [self.tableView rectForSection:1].origin.y - offset;
bottomCellOffset = floorf(bottomCellOffset);

if (scrollView.contentOffset.y >= bottomCellOffset) {
scrollView.contentOffset = CGPointMake(0, bottomCellOffset);
if (self.canScroll) {
self.canScroll = NO;
self.containerCell.objectCanScroll = YES;
}
}else{
//子视图没到顶部
if (!self.canScroll) {
scrollView.contentOffset = CGPointMake(0, bottomCellOffset);
}
}
}
}

至此,功能大体上就完活了。不过还是有需要优化的地方,也就是第四个重点。当我们在横向滑动来切换segment时,有时候会触发使得纵向也滑动了,很不好的体验。所以需要在容器cell中,横向滑动的时候,禁止纵向滑动

1
2
3
4
5
6
7
8
9
10
11
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
// 为了横向滑动的时候,外层的tableView不动
if (!self.isSelectIndex) {
if (scrollView == self.scrollView) {
if (self.delegate &&
[self.delegate respondsToSelector:@selector(mmtdOptionalScrollViewDidScroll:)]) {
[self.delegate mmtdOptionalScrollViewDidScroll:scrollView];
}
}
}
}

另外一个需要优化的地方,重点5,是当数据多的一页滑到中间,这时候切换segment,换到数据少的一页,向下拉动一点,再换回刚刚那个滑到中间的那一页(gif演示中最后一部分)。需要在容器cell里做如下操作,否则会闪一下。

1
2
3
4
5
6
7
8
9
10
11
12
13
- (void)setObjectCanScroll:(BOOL)objectCanScroll {
_objectCanScroll = objectCanScroll;

self.oneVC.vcCanScroll = objectCanScroll;
self.twoVC.vcCanScroll = objectCanScroll;
self.threeVC.vcCanScroll = objectCanScroll;

if (!objectCanScroll) {
[self.oneVC.tableView setContentOffset:CGPointZero animated:NO];
[self.twoVC.tableView setContentOffset:CGPointZero animated:NO];
[self.threeVC.tableView setContentOffset:CGPointZero animated:NO];
}
}

最后一点,切记,mainTableView的bounces可以设置为NO,但是,listTableView的bounces一定得是YES,否则偏移量会有那么一丝丝误差使得滑不动。