ScrollView和TableView无缝拼接滑动实现方案

最近在做一个新需求:将原有的三个视图纵向拼接展示,超过屏幕高度时可滑动。

示意图如下:

非常简单有木有,然而事情并没有想象的那么简单。

首先看一下三个view都是什么,View A 内部包含一个UIScrollView,高度不固定;View B 就是普通的视图,高度不超过屏幕高;View C 内部包含一个UITableView,高度也不固定。

上面的示意图略有点丑,我找一个好看点的例子。PS:我不是美团的,就是用一下图哈哈。

图1:

图2:

图3:

图4:

图1+图2和在一起可以理解成是上面的View A;图3中的一小条可以理解成是上面的View B;图4中的一些推荐可以理解成是上面的View C。

所以能怎么做呢?最简单的方法就是用一个大的UITableView搞定(假设叫做fullTableView)。在View A和View B的外面包一个大的View(假设叫做headerView),然后将headerView赋值给fullTableView的tableHeaderView属性。

这样做确实可以达到目的,但不是最优解。为什么?因为,这三个View是由不同的三个童鞋开发的,之后也会由他们分别维护。如果按照上面的方法,对原有View的侵入性太深,且任一View改动之后就可能会影响到总体的fullTableView,可维护性性不佳。

那怎样做更优呢?

让View A暴露两个属性:

@property (nonatomic, strong, readonly) UIScrollView *scrollView;
@property (nonatomic, assign, readonly) CGFloat fullContentHeight;

fullContentHeight等于scrollView的contentSize.height的值。

同理让View C也暴露两个属性:

@property (nonatomic, strong, readonly) UIScrollView *scrollView;
@property (nonatomic, assign, readonly) CGFloat fullContentHeight;

同时,定义一个协议:

@protocol STBottomTableViewDataSource <NSObject>
- (UIView *)bottomTableHeaderView;
@end

供View C中的TableView获取tableHeaderView。

修改其初始话方法:

- (instancetype)initWithFrame:(CGRect)frame dataSource:(id<STBottomTableViewDataSource>)dataSource;

至此,准备工作就做好了。接下来步入正题:

在最外层创建一个大的ScrollView:

@property (nonatomic, strong) UIScrollView *containerScrollView;

将View A(topView)和View C(bottomView)分别加到其上面:

[self.containerScrollView addSubview:self.topView];
[self.containerScrollView addSubview:self.bottomView];

然后设置其contentSize值:

self.containerScrollView.contentSize = CGSizeMake(self.containerScrollView.bounds.size.width, self.topView.fullContentHeight + self.bottomView.fullContentHeight);

最后在其代理方法中做一些设置(分别更新两个View的top值和contentOffset.y值):

- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    if (scrollView == self.containerScrollView) {
        [self refreshHeaderAndFooterFrame];
    }
}

- (void)refreshHeaderAndFooterFrame {
    CGFloat containerOffsetY = self.containerScrollView.contentOffset.y;
    CGFloat maxTopOffsetY = MAX(self.topView.fullContentHeight - self.topView.height, 0);
    CGFloat realTopOffsetY = MIN(MAX(containerOffsetY, 0), maxTopOffsetY);
    if (realTopOffsetY != self.topView.top) {
        self.topView.top = realTopOffsetY;
    }
    if (realTopOffsetY != self.topView.scrollView.contentOffset.y) {
        [self scrollView:self.topView.scrollView setContentOffset:CGPointMake(0, realTopOffsetY)];
    }

    CGFloat maxbottomY = MAX(self.topView.fullContentHeight, containerOffsetY);
    CGFloat realBottomOffsetY = MAX(0, containerOffsetY - self.topView.fullContentHeight);
    if (maxbottomY != self.bottomView.top) {
        self.bottomView.top = maxbottomY;
    }
    if (realBottomOffsetY != self.bottomView.scrollView.contentOffset.y) {
        [self scrollView:self.bottomView.scrollView setContentOffset:CGPointMake(0, realBottomOffsetY)];
    }
}

- (void)scrollView:(UIScrollView *)scrollView setContentOffset:(CGPoint)offset {
    scrollView.contentOffset = offset;
}

至此,大功告成,其实也很简单。当然实际情况更加复杂,比如说View A 和 View C的高度会动态变化,可以加个KVO嘛。

更多的实现细节就不说了,项目完整版请猛戳这里