首先,新的一年,懷有期待的努力著!這兩個月,晝夜不分. 看著自己寫的代碼,才意識到日子是切切實實的過了的.
這個主題我很早很早就想寫的. 然后就沒有然后了. 這次開發(fā)中正好有這個需求,那當(dāng)然是不能再錯過的. 主要記錄實現(xiàn)的原理以及遇到的問題和解決方案.
1. 頁面展示
定義:
-
MainTableView:就是用戶看到的滾動視圖,mainTableView的父視圖就是self.view. -
ContentTableViewCell:就是tableView的cell,和普通的自定義cell一樣.這個里面放的就是我們的HeaderView,當(dāng)然你也可以用tableHeaderView來實現(xiàn).Cell有一個好處就是自適應(yīng)內(nèi)容的高度,并且很容易的取到這個高度. -
ContentView:這個是ContentTableViewCell的真實的ContentView,這就是一個簡單的UIView,但里面的子視圖是一個UICollectionView. -
TitleView:這就是items的View,這里把這個View作為TableView的sectionHeaderView來實現(xiàn). -
ContentCollectionView:最上層用來展示數(shù)據(jù)的collectionView,根據(jù)需求用collectionView還是tableView,一般來說都是一個列表.
相信寫到這里,你已經(jīng)知道了大概的實現(xiàn)原理了吧. 實現(xiàn)的最終效果如下.

2. 實現(xiàn)思路
實現(xiàn)這個頁面,最重要的問題是要解決手勢沖突.監(jiān)聽MainTableView和contentCollectionView的滾動方法來控制彼此的ContentOffset. MainTableView和contentCollectionView之間的通信有多種方式.通知或者代理.這里采用的是代理的方法,用代理,在同一個VC里實現(xiàn)滾動的監(jiān)聽,看起來更清楚一些.
2.1 實現(xiàn)下面方法滿足同時監(jiān)聽多個相同手勢
相同類型的手勢,同一時間只有一個能夠得到辨認(rèn).下面這個方法返回Yes也就意味著所有相同類型的手勢都能得到處理.
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
return YES;
}
2.2 MainTableView的滾動監(jiān)聽
臨界點就是需要懸停的位置.也就是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已經(jīng)滾動不見,開始滾動某一個listView,那么固定mainTableView的contentOffset,讓其不動.
_contentTableView.contentOffset = CGPointMake(0, bottomCellOffset);
}
//mainTableView已經(jīng)顯示了header,listView的contentOffset需要重置.
if (scrollView.contentOffset.y < bottomCellOffset) {
if(_currentScrollingListView.contentOffset.y > 0) {
_currentScrollingListView.contentOffset = CGPointZero;
}
}
}
2.3 contentCollectionView的滾動監(jiān)聽
這里用的是代理.所以這個方法也是在MainViewController里.記錄當(dāng)前滾動的ListView.通過與lastScrollingListViewContentOffsetY的比較可以判斷contentCollectionView是向上滾動,還是向下滾動.這里有一個問題:需要設(shè)置MainTableView的bouces = NO,如果沒有設(shè)置這個的話,那么_contentTableView.contentOffset.y == 0幾乎就不會執(zhí)行.但是這個設(shè)置為NO的同時也會引發(fā)另一個問題,這個在下面的問題中列出.
其實簡單的說就是以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;
}
另外,關(guān)于CollectionView的定點問題.有時間我再整理一下.
[self.collectionView scrollToItemAtIndexPath:[NSIndexPath indexPathForRow:contentViewCurrentIndex inSection:0] atScrollPosition:UICollectionViewScrollPositionNone animated:NO];
3. 一些問題和解決方案
3.1 當(dāng)contentCollectionView沒有數(shù)據(jù)源時,滾動到懸停位置后無法滑動到原來的狀態(tài)?
當(dāng)列表沒有數(shù)據(jù)時就檢測不到滑動手勢??梢愿鶕?jù)需要,或者在網(wǎng)絡(luò)請求的回調(diào)中做判斷,如果沒有數(shù)據(jù)或者網(wǎng)絡(luò)請求失敗等情況添加contentCollectionView的noDataView.
3.2 上滑之后會出現(xiàn)第一次點擊cell不響應(yīng)點擊事件???
初始化tableView的時候添加以下代碼.cancelsTouchesInView設(shè)置為NO,當(dāng)兩個事件有沖突時,都會響應(yīng)兩個事件.所以這個設(shè)置為NO可以解決很多的問題.
_contentTableView.panGestureRecognizer.cancelsTouchesInView = NO;
3.3 如何動態(tài)顯示titleView item的個數(shù)?
這個在網(wǎng)絡(luò)請求回調(diào)中初始化TitleArray就可以了.這個有什么問題呢?
3.4 關(guān)于刷新?
其實還是上面關(guān)于臨界點的問題.如何做到下拉的時候MainTableView固定不動,而contentCollectionView可以滾動.所以這個地方,就必須要設(shè)置MainTableView的bonces.上面的代碼就已經(jīng)可以實現(xiàn)了.
3.5 mainTableView.bounces = NO;設(shè)置后滾動到一定位置后無法滾動?
一般我們都是通過懶加載的方式去創(chuàng)建控件,如果在這個時候去設(shè)置frame,那么這個frame一經(jīng)設(shè)置是不會改變的.所以需要在下面這個方法里面設(shè)置tableView的frame.viewDidLayoutSubviews方法是當(dāng)Controller的子視圖的positon和frame發(fā)生改變時會被調(diào)用.也就是執(zhí)行完AutoLayout后會調(diào)用這個方法.
- (void)viewDidLayoutSubviews {
[super viewDidLayoutSubviews];
self.contentTableView.frame = self.view.bounds;
}
3.6 滾動的時候,會重新調(diào)用viewDidLoad方法?(這個是網(wǎng)上的一個Demo,之前我也有參考)
如果你也遇到同樣的問題. 試試不要用懶加載的方式去創(chuàng)建MainTableView.這里的問題是因為在監(jiān)聽滾動的時候,vc.contentCollectionView.contentOffset會導(dǎo)致subView被添加到self.view上從而引發(fā)了viewDidLoad方法的調(diào)用.具體的原因還需要深度剖析.但不通過懶加載可以解決這個問題.
for (id vc in _viewControllers) {
vc.vcCanScroll = cellCanScroll;
if (!cellCanScroll) {
vc.contentCollectionView.contentOffset = CGPointZero;
}
}
4. 巨人的肩膀
5. 總結(jié)
以上,就是關(guān)于tableView的嵌套使用.基本上遇到的問題我都列出來了,后續(xù)如果我再發(fā)現(xiàn)其它的問題也會補上,最近有不少想要總結(jié)的點,但真的太忙了,一有時間就會更新的。終于完成了這一篇博客!2019!