最近在做一个新需求:将原有的三个视图纵向拼接展示,超过屏幕高度时可滑动。
示意图如下:
非常简单有木有,然而事情并没有想象的那么简单。
首先看一下三个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嘛。
更多的实现细节就不说了,项目完整版请猛戳这里。