首先,新的一年,怀有期待的努力着!这两个月,昼夜不分. 看着自己写的代码,才意识到日子是切切实实的过了的.
这个主题我很早很早就想写的. 然后就没有然后了. 这次开发中正好有这个需求,那当然是不能再错过的. 主要记录实现的原理以及遇到的问题和解决方案.
1. 页面展示
定义:
-
MainTableView:
就是用户看到的滚动视图,mainTableView的父视图就是self.view. -
ContentTableViewCell:
就是tableView的cell,和普通的自定义cell一样.这个里面放的就是我们的HeaderView,当然你也可以用tableHeaderView来实现.Cell有一个好处就是自适应内容的高度,并且很容易的取到这个高度. -
ContentView:
这个是ContentTableViewCell的真实的ContentView,这就是一个简单的UIView,但里面的子视图是一个UICollectionView. -
TitleView:
这就是items的View,这里把这个View作为TableView的sectionHeaderView来实现. -
ContentCollectionView:
最上层用来展示数据的collectionView,根据需求用collectionView还是tableView,一般来说都是一个列表.
相信写到这里,你已经知道了大概的实现原理了吧. 实现的最终效果如下.
2. 实现思路
实现这个页面,最重要的问题是要解决手势冲突.监听MainTableView和contentCollectionView的滚动方法来控制彼此的ContentOffset. MainTableView和contentCollectionView之间的通信有多种方式.通知或者代理.这里采用的是代理的方法,用代理,在同一个VC里实现滚动的监听,看起来更清楚一些.
2.1 实现下面方法满足同时监听多个相同手势
相同类型的手势,同一时间只有一个能够得到辨认.下面这个方法返回Yes也就意味着所有相同类型的手势都能得到处理.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
2.2 MainTableView的滚动监听
临界点就是需要悬停的位置.也就是HeaderView刚好消失的位置.这里用的Cell,就可以直接用 bottomCellOffset = [_contentTableView rectForSection:1].origin.y
取到Header的高度.如果bottomCellOffset > offSetY,说明到了顶处,这个时候就固定MainTableView的contentOffSet,让MainTableView保持不动.
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
CGFloat bottomCellOffset = [_contentTableView rectForSection:1].origin.y;
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
//mainTableView的header已经滚动不见,开始滚动某一个listView,那么固定mainTableView的contentOffset,让其不动.
_contentTableView.contentOffset = CGPointMake(0, bottomCellOffset);
}
//mainTableView已经显示了header,listView的contentOffset需要重置.
if (scrollView.contentOffset.y < bottomCellOffset) {
if(_currentScrollingListView.contentOffset.y > 0) {
_currentScrollingListView.contentOffset = CGPointZero;
}
}
}
2.3 contentCollectionView的滚动监听
这里用的是代理.所以这个方法也是在MainViewController里.记录当前滚动的ListView.通过与lastScrollingListViewContentOffsetY的比较可以判断contentCollectionView是向上滚动,还是向下滚动.这里有一个问题:
需要设置MainTableView的bouces = NO,如果没有设置这个的话,那么_contentTableView.contentOffset.y == 0几乎就不会执行.但是这个设置为NO的同时也会引发另一个问题,这个在下面的问题中列出.
其实简单的说就是以HeaderView的高度为临界点,在HeaderView消失之前,就让MainTableView滚动,而让contentCollectionView固定不动.反之,就让contentCollectionView滚动,MainTableView固定不动.
- (void)contentCollectionViewDidScroll:(UICollectionView *)contentCollectionView
{
self.currentScrollingListView = contentCollectionView;
CGFloat bottomCellOffset = [_contentTableView rectForSection:1].origin.y;
BOOL shouldProcess = YES;
if (contentCollectionView.contentOffset.y > self.lastScrollingListViewContentOffsetY) {
}
else {
if(_contentTableView.contentOffset.y == 0) shouldProcess = NO;
else {
if(_contentTableView.contentOffset.y < bottomCellOffset) {
_currentScrollingListView.contentOffset = CGPointZero;
}
}
}
if (shouldProcess) {
if (_contentTableView.contentOffset.y < bottomCellOffset) {
if (_currentScrollingListView.contentOffset.y > 0) {
_currentScrollingListView.contentOffset = CGPointZero;
}
}
else {
_contentTableView.contentOffset = CGPointMake(0, bottomCellOffset);
}
}
self.lastScrollingListViewContentOffsetY = self.currentScrollingListView.contentOffset.y;
}
另外,关于CollectionView的定点问题.有时间我再整理一下.
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:contentViewCurrentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
3. 一些问题和解决方案
3.1 当contentCollectionView没有数据源时,滚动到悬停位置后无法滑动到原来的状态?
当列表没有数据时就检测不到滑动手势。可以根据需要,或者在网络请求的回调中做判断,如果没有数据或者网络请求失败等情况添加contentCollectionView的noDataView.
3.2 上滑之后会出现第一次点击cell不响应点击事件???
初始化tableView的时候添加以下代码.cancelsTouchesInView设置为NO,当两个事件有冲突时,都会响应两个事件.所以这个设置为NO可以解决很多的问题.
_contentTableView.panGestureRecognizer.cancelsTouchesInView = NO;
3.3 如何动态显示titleView item的个数?
这个在网络请求回调中初始化TitleArray就可以了.这个有什么问题呢?
3.4 关于刷新?
其实还是上面关于临界点的问题.如何做到下拉的时候MainTableView固定不动,而contentCollectionView可以滚动.所以这个地方,就必须要设置MainTableView的bonces.上面的代码就已经可以实现了.
3.5 mainTableView.bounces = NO;设置后滚动到一定位置后无法滚动?
一般我们都是通过懒加载的方式去创建控件,如果在这个时候去设置frame,那么这个frame一经设置是不会改变的.所以需要在下面这个方法里面设置tableView的frame.viewDidLayoutSubviews方法是当Controller的子视图的positon和frame发生改变时会被调用.也就是执行完AutoLayout后会调用这个方法.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.contentTableView.frame = self.view.bounds;
}
3.6 滚动的时候,会重新调用viewDidLoad方法?(这个是网上的一个Demo,之前我也有参考)
如果你也遇到同样的问题. 试试不要用懒加载的方式去创建MainTableView.这里的问题是因为在监听滚动的时候,vc.contentCollectionView.contentOffset会导致subView被添加到self.view上从而引发了viewDidLoad方法的调用.具体的原因还需要深度剖析.但不通过懒加载可以解决这个问题.
for (id vc in _viewControllers) {
vc.vcCanScroll = cellCanScroll;
if (!cellCanScroll) {
vc.contentCollectionView.contentOffset = CGPointZero;
}
}
4. 巨人的肩膀
5. 总结
以上,就是关于tableView的嵌套使用.基本上遇到的问题我都列出来了,后续如果我再发现其它的问题也会补上,最近有不少想要总结的点,但真的太忙了,一有时间就会更新的。终于完成了这一篇博客!2019!