iOS UITableView+UICollectionView嵌套,手勢沖突解決

首先,新的一年,懷有期待的努力著!這兩個月,晝夜不分. 看著自己寫的代碼,才意識到日子是切切實實的過了的.
這個主題我很早很早就想寫的. 然后就沒有然后了. 這次開發(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)的最終效果如下.


output.gif

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!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容