某一天接到一個(gè)需求,要求做一個(gè)類似微博發(fā)現(xiàn)頁的頁面,需要支持上下滑動(dòng)的同時(shí)可以左右滑動(dòng)展示更多的內(nèi)容,就像下圖這樣

藍(lán)框下方的部分可以左右滑動(dòng),藍(lán)框以及藍(lán)框上邊的部分不可以左右滑,點(diǎn)擊藍(lán)框中的title藍(lán)框下方會(huì)切換不同的頁面,左右滑藍(lán)框下方的頁面藍(lán)框里的title也會(huì)實(shí)時(shí)變化為高亮,往上滑的時(shí)候藍(lán)框內(nèi)的部分會(huì)吸頂
大致的實(shí)現(xiàn)方法是用一個(gè)大的TableView實(shí)現(xiàn),這個(gè)頁面大概可以分為三部分

- 第一部分是固定的,其實(shí)就是tableView的HeadView
- 第二部分(暫且叫做categoryView) 其實(shí)是tableView的第一個(gè)section的headerView,內(nèi)部有一個(gè)點(diǎn)擊切換的效果
- 第三部分是tableView的一個(gè)Cell,Cell內(nèi)部是一個(gè)UICollectionView,可以左右滑動(dòng),滑動(dòng)時(shí)與categoryView聯(lián)動(dòng),collectionView的每一個(gè)cell是一個(gè)TableView,在categoryView吸頂?shù)臅r(shí)候可以自由滑動(dòng)
難題一:當(dāng)視圖滑動(dòng)一部分之后,手指在第三部分滑動(dòng)的時(shí)候,會(huì)觸發(fā)第三部分視圖內(nèi)的某個(gè)TableView的滑動(dòng),這時(shí)候外部的大的tableView就會(huì)失去滑動(dòng)效果,想要滑動(dòng)只能是滑動(dòng)第三部分上方的視圖。
解決方案:將最外部的tableView變成新建的一個(gè)tableView類,(繼承自UITableView)然后將它的- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer方法重寫
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return [gestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]] && [otherGestureRecognizer isKindOfClass:[UIPanGestureRecognizer class]];
}
這樣可以保證你在滑動(dòng)第三部分里的小的tableView時(shí),外部的大的tableView依然可以滑動(dòng),但這時(shí)候這兩種滑動(dòng)會(huì)重疊,也就是說你滑動(dòng)第三部分的tableView時(shí),大的tableView也在滑動(dòng),我們要的效果是無論何時(shí)只有一個(gè)tableView在滑動(dòng),所以我們要實(shí)時(shí)監(jiān)測這兩個(gè)tableView的偏移量,當(dāng)一個(gè)tableView滑動(dòng)的時(shí)候讓另一個(gè)tableView的contentOffset始終為0,造成一種不動(dòng)的錯(cuò)覺,分兩種情況:
- 第二部分沒有吸頂?shù)臅r(shí)候,保持外部的大tableView滑動(dòng)效果,小tableView雖然也可以滑動(dòng),但是要造成它不動(dòng)的效果
- 第二部分吸頂?shù)臅r(shí)候,只讓小的tableView滑動(dòng),外部的大tableView要造成不動(dòng)的效果
同時(shí)檢測這兩個(gè)tableView的代理方法- (void)scrollViewDidScroll:(UIScrollView *)scrollView,針對(duì)大的TableView:
if (self.currentScrollingListView != nil && self.currentScrollingListView.contentOffset.y > 0) {
//mainTableView的header已經(jīng)滾動(dòng)不見,開始滾動(dòng)某一個(gè)listView,那么固定mainTableView的contentOffset,讓其不動(dòng)
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
}
if (scrollView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView已經(jīng)顯示了header,listView的contentOffset需要重置
NSArray *listViews = [self.delegate listViewsInPagerView:self];
for (UIView <JXPagerViewListViewDelegate>* listView in listViews) {
[listView listScrollView].contentOffset = CGPointZero;
}
}
針對(duì)小的tableView
if (self.mainTableView.contentOffset.y < [self.delegate tableHeaderViewHeightInPagerView:self]) {
//mainTableView的header還沒有消失,讓listScrollView一直為0
scrollView.contentOffset = CGPointZero;
scrollView.showsVerticalScrollIndicator = NO;
}else {
//mainTableView的header剛好消失,固定mainTableView的位置,顯示listScrollView的滾動(dòng)條
self.mainTableView.contentOffset = CGPointMake(0, [self.delegate tableHeaderViewHeightInPagerView:self]);
scrollView.showsVerticalScrollIndicator = YES;
}
這樣就可以實(shí)現(xiàn)一種自動(dòng)吸頂同時(shí)又比較順滑的滑動(dòng)效果
難題二: categoryView與第三部分多個(gè)View的聯(lián)動(dòng)效果
categoryView的實(shí)現(xiàn)方式是用一個(gè)collectionView實(shí)現(xiàn)的,下方第三部分也是一個(gè)collectionView, 在封裝第二部分視圖的時(shí)候,里邊有一個(gè)UIScrollview類型的屬性,將第三部分的collectionView賦給這個(gè)屬性,然后通過KVO監(jiān)聽第三部分collectionView的滑動(dòng),就可以將第二部分的collectionView的cell的點(diǎn)擊事件和第三個(gè)collectionView的滑動(dòng)關(guān)聯(lián)起來
[contentScrollView addObserver:self forKeyPath:@"contentOffset" options:NSKeyValueObservingOptionNew context:nil];
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context
{
if ([keyPath isEqualToString:@"contentOffset"] && (self.contentScrollView.isTracking || self.contentScrollView.isDecelerating)) {
//用戶滾動(dòng)引起的contentOffset變化,才處理。
CGPoint contentOffset = [change[NSKeyValueChangeNewKey] CGPointValue];
[self contentOffsetOfContentScrollViewDidChanged:contentOffset];
}
}
這里只是記錄了大體的思路,來理解這個(gè)效果是怎樣實(shí)現(xiàn)的,其實(shí)還有很多需要注意的細(xì)節(jié),源代碼是兩個(gè)第三方JXCategoryView和 JXPagingView