OC-抖音上拉加載(你以為單純用MJRefresh就能實(shí)現(xiàn)?那你就錯(cuò)了)

2018-8-16修改:
根據(jù)@劉奇林同學(xué)的疑問(wèn),pageebable和mjfooter沒(méi)有沖突,所以我再次測(cè)試了我的代碼。

將scrollViewDidScroll里面關(guān)閉pageebable注釋掉,測(cè)試后發(fā)現(xiàn)mjfooter顯示正常(此時(shí)重設(shè)_tableView.contentOffset的代碼還在)。

于是我就想是不是pageebable和mjfooter真的沒(méi)有沖突,如果沒(méi)有沖突的話(huà)那那些判斷上拉還是下拉,以及重設(shè)_tableView.contentOffset的代碼是不是都沒(méi)有用了呢?

我就將_tableView.contentOffset的代碼也注釋掉,滑動(dòng)發(fā)現(xiàn)頁(yè)面不正常了,第一次上拉加載的時(shí)候mjfooter能正常顯示,第二次加載,觸發(fā)mj后頁(yè)面就回彈了,并且加載成功后頁(yè)面下移了44像素。

到底是哪兒出了問(wèn)題呢?明明注掉pageebable開(kāi)關(guān)的代碼時(shí)是沒(méi)問(wèn)題的呀?看來(lái)還是需要設(shè)置_tableView.contentOffset。

我將加載成功后重置_tableView.contentOffset的代碼解開(kāi),加載成功后頁(yè)面不再下移,但footer依然顯示不正常,我又將scrollViewWillBeginDecelerating中設(shè)置contentOffset的代碼解開(kāi),依然不好使,后來(lái)發(fā)現(xiàn)scrollViewWillBeginDecelerating中的代碼只有在_tableView.updating=yes的時(shí)候才會(huì)走(???♀????♀????♀????♀?),于是又解開(kāi)了scrollViewDidScroll中的代碼,一切都正常了。

但我又發(fā)現(xiàn)了另一個(gè)問(wèn)題,當(dāng)正在上拉加載時(shí),用戶(hù)又下滑列表了,雖然頁(yè)面還是page的方式滑動(dòng),但是會(huì)錯(cuò)位,因?yàn)樵谀玫椒祷財(cái)?shù)據(jù)之前_tableView.updating一直是yes,也就是一直會(huì)執(zhí)行scrollViewWillBeginDecelerating中將頁(yè)面上移44像素的代碼。

那是不是可以加個(gè)判斷,只有上拉加載時(shí)將頁(yè)面上移,上拉加載沒(méi)拿到返回值時(shí)用戶(hù)下拉就不將頁(yè)面上移,就正常顯示即可。最后將scrollViewDidScroll中判斷上拉還是下拉的代碼挪到scrollViewWillBeginDecelerating中,當(dāng)上拉觸發(fā)mj時(shí)頁(yè)面上移,頁(yè)面下拉時(shí)不做任何操作即可。大功告成

感謝@劉奇林同學(xué)的質(zhì)疑,讓我省去了控制pageebable開(kāi)關(guān)的一系列代碼,稍微精簡(jiǎn)了一下代碼?? 這種質(zhì)疑和求真的心態(tài)很棒哦??????????????????

代碼修改如下:

先上DEMO記得star哦

效果圖

之前實(shí)現(xiàn)了抖音下拉刷新效果之后就沒(méi)再繼續(xù)研究,想著上拉加載隨便集成一下MJRefresh就可以了,很簡(jiǎn)單嘛,等需要的時(shí)候再加進(jìn)去就好了。
直到某一天有個(gè)小伙伴跟我說(shuō)添加上拉加載有問(wèn)題,我就不信了,怎么可能呢,自己試了試還真是不少坑。

現(xiàn)在想來(lái)之前真的是圖樣圖森破,你以為你以為的就是你以為的?實(shí)踐出真知,萬(wàn)事都不能想當(dāng)然,只有自己真的去操作了才能明白。

下面咱們來(lái)一起實(shí)現(xiàn)一下:
看之前先熟悉一下OC-仿抖音下拉刷新,操作是基于之前的demo的

1、添加上拉加載-使用延時(shí)模擬請(qǐng)求
self.tableView.mj_footer = [MJRefreshBackNormalFooter footerWithRefreshingBlock:^{
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            [weakSelf getMoreData];
        });
        
    }];
加上mj_footer之后運(yùn)行起來(lái),發(fā)現(xiàn)根本觸發(fā)不了mj,因?yàn)橹按a里默認(rèn)關(guān)閉了tableView的bounces,滑到最后不打開(kāi)彈性效果是沒(méi)辦法往上拖拽觸發(fā)mj。

一開(kāi)始的想法是判斷當(dāng)前是否播放到列表的最后一個(gè)cell,如果是就打開(kāi),不是就關(guān)閉。后來(lái)想想其實(shí)只有第一個(gè)cell的時(shí)候才需要關(guān)閉彈性效果,就改成了下面代碼:

//此方法每次cell播放的時(shí)候都會(huì)掉用,所以寫(xiě)在了這里,實(shí)時(shí)判斷
- (void)tableView:(UITableView *)tableView willPlayVideoOnCell:(UITableViewCell *)cell {
    VideoTableViewCell *Cell = (VideoTableViewCell *)cell;
    Cell.playButton.selected = NO;
    playIndex = (int)Cell.playButton.tag;
    [cell.jp_videoPlayView jp_resumeMutePlayWithURL:cell.jp_videoURL
                                 bufferingIndicator:nil
                                       progressView:nil
                            configurationCompletion:^(UIView * _Nonnull view, JPVideoPlayerModel * _Nonnull playerModel) {
                                view.jp_muted = NO;
                            }];
    if (Cell.playButton.tag==0) {
        //列表第一個(gè)cell時(shí)關(guān)閉
        self.tableView.bounces = NO;
    }else
        self.tableView.bounces = YES;
}
2、pagingEnabled和mj的矛盾

按照第一步,調(diào)整bounces的開(kāi)關(guān)后,確實(shí)能夠觸發(fā)mj了,但是由于pagingEnabled的回彈效果,每次上拉觸發(fā)mj后頁(yè)面又回滾回去,沒(méi)有正常上拉加載時(shí)footer懸停然后轉(zhuǎn)圈的效果,導(dǎo)致用戶(hù)根本看不見(jiàn)mj的加載狀態(tài),并且加載完成后reloadData,cell的位置會(huì)錯(cuò)亂,不是剛好整屏整屏的顯示,有偏移。

1)先解決下回滾的問(wèn)題

既然pagingEnabled影響了加載效果的顯示,那是不是也可以通過(guò)手段來(lái)控制pagingEnabled的開(kāi)關(guān)呢?

于是我開(kāi)始監(jiān)控tableView的滑動(dòng),通過(guò)playIndex記錄當(dāng)前播放到第幾個(gè)cell,當(dāng)播放到最后一個(gè)cell,用戶(hù)又繼續(xù)上滑說(shuō)明用戶(hù)正在上拉加載,此時(shí)是關(guān)閉pagingEnabled的最佳時(shí)機(jī),具體代碼如下,有注釋?zhuān)?/p>

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [self.tableView jp_scrollViewDidScroll];
    int index= (int)self.tableView.contentOffset.y/kHeight;
    //scroll是與整屏相比的偏移量,肯定是正的
    float scroll = self.tableView.contentOffset.y- index*kHeight;
    //與上一個(gè)滑動(dòng)點(diǎn)比較,區(qū)分上滑還是下滑
    float offset = self.tableView.contentOffset.y- oldOffset.y;
    //記錄當(dāng)前tableView.contentOffset
    oldOffset = self.tableView.contentOffset;
    if (offset>0) {
        //上拉-44是mj_footer的高度,當(dāng)拖拽超過(guò)44的時(shí)候會(huì)觸發(fā)mj
        if (playIndex==_tableView.items.count-1&&scroll>44) {
            if (_updating==NO) {
                //判斷是否正在刷新,正在刷新就不再進(jìn)行如下設(shè)置,以免重復(fù)加載
                _updating = YES;
                //進(jìn)到這里說(shuō)明用戶(hù)正在上拉加載,觸發(fā)mj,此時(shí)要關(guān)閉翻頁(yè)功能否則頁(yè)面回彈mj_footer就看不到了,setContentOffset也無(wú)效
                self.tableView.pagingEnabled = NO;
                //給tableView設(shè)置一個(gè)固定的Offset,往上偏移點(diǎn),將footer展示出來(lái),要大于44才會(huì)觸發(fā)footer
                [self.tableView setContentOffset:CGPointMake(0, index*kHeight+50) animated:NO];
                [self.tableView.mj_footer beginRefreshing];
            } 
        }
    }
}

然后在加載結(jié)束后再打開(kāi)pagingEnabled即可,這樣就能看到mj的加載狀態(tài)了,抖音加載結(jié)束后會(huì)自動(dòng)滾動(dòng)到下一個(gè)cell播放,所以我也做了此操作,但是自動(dòng)滾動(dòng)后就會(huì)發(fā)現(xiàn)當(dāng)前cell總是向上偏移了一部分,露出當(dāng)前cell的下一個(gè)cell,如圖:
紅框內(nèi)偏移高度
2)解決偏移問(wèn)題

我打印了cell自動(dòng)滾動(dòng)后self.tableView.contentOffset.y,與整屏偏移量是44,也就是說(shuō)頁(yè)面剛好上移了44像素,那不就是footer的高度嗎?于是我在自動(dòng)滾動(dòng)后讓tableView.contentOffset.y再下移44,矯正過(guò)來(lái),發(fā)現(xiàn)好使,代碼如下:

-(void)getMoreData
{
    _updating=NO;
    [self.tableView.mj_footer endRefreshing];
    int index = (int)self.pathStrings.count;
    [self.pathStrings addObjectsFromArray:@[@"http://p11s9kqxf.bkt.clouddn.com/coder.mp4",@"http://p11s9kqxf.bkt.clouddn.com/cat.mp4",@"http://p11s9kqxf.bkt.clouddn.com/coder.mp4",@"http://p11s9kqxf.bkt.clouddn.com/cat.mp4"]];
    [self.tableView reloadData];
    //滾動(dòng)到下一個(gè)cell
    [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0] atScrollPosition:UITableViewScrollPositionBottom animated:NO];
    NSLog(@"1w:%.f",self.tableView.contentOffset.y);
    //mjfooter高度是44,上拉加載時(shí)頁(yè)面會(huì)向上偏移44像素,數(shù)據(jù)加載完畢后需要將contentOffset復(fù)位
    self.tableView.contentOffset =CGPointMake(0, self.tableView.contentOffset.y-44);
    NSLog(@"%.f",self.tableView.contentOffset.y);
    //讓cell開(kāi)始播放
    VideoTableViewCell *cell = [self.tableView cellForRowAtIndexPath:[NSIndexPath indexPathForRow:index inSection:0]];
    [self tableView:self.tableView willPlayVideoOnCell:cell];
    //刷新結(jié)束,開(kāi)啟翻頁(yè)功能
    self.tableView.pagingEnabled = YES;
}
3、ios11適配問(wèn)題

根據(jù)上面,小偏移問(wèn)題解決了,可是我繼續(xù)再上滑查看后面cell的時(shí)候,詭異的事情發(fā)生了,后面的偏移更大了,而且滑動(dòng)到底部就再也拽不動(dòng)了:
紅框內(nèi)偏移部分有點(diǎn)大
這就有點(diǎn)傻眼了,這偏移的過(guò)分了點(diǎn),我輕輕滑動(dòng)頁(yè)面,打印tableView減速后contentOffset.y的數(shù)值,發(fā)現(xiàn)數(shù)值沒(méi)問(wèn)題,剛好是屏幕高度的整數(shù)倍,一點(diǎn)都沒(méi)偏移。

這是什么情況?翻頁(yè)效果、回彈效果都沒(méi)問(wèn)題,contentOffset.y也對(duì)著呢,就是cell顯示的不正常,難道是pagingEnabled在反復(fù)開(kāi)關(guān)、設(shè)置contentOffset.y之后有點(diǎn)神經(jīng)錯(cuò)亂了?

于是我嘗試了各種辦法,徹底關(guān)閉pagingEnabled正常滑動(dòng)cell、修改cell的高度不讓它全屏在進(jìn)行測(cè)試、通過(guò)監(jiān)控滑動(dòng)過(guò)程自己模擬翻頁(yè)效果、翻看老項(xiàng)目非全屏cell滑動(dòng)加載效果……所有測(cè)試失敗后我又回退到一開(kāi)始的樣子

啊啊啊啊,總之我折騰了一天,感覺(jué)要瘋的節(jié)奏,眼看快下班了,要不真機(jī)試試吧,說(shuō)不定是模擬器有問(wèn)題呢?(當(dāng)時(shí)我的手機(jī)是11.3,剛升級(jí),xcode是9.2不支持,升級(jí)xcode太費(fèi)時(shí)間了,所以一直在用模擬器測(cè)試)

從同事那借了個(gè)低版本的手機(jī),10.3的,運(yùn)行后,完美,拖拽,刷新,加載,復(fù)位一點(diǎn)問(wèn)題都沒(méi)有,很順暢。(此時(shí)的我心中一萬(wàn)個(gè)草泥馬奔騰而過(guò)……模擬器的鍋,讓我浪費(fèi)了一天)

沒(méi)問(wèn)題了,就開(kāi)心的下班了。

第二天到了公司,想著要不給xcode升個(gè)級(jí)吧,總借手機(jī)用怪麻煩的,然后是漫長(zhǎng)的等待升級(jí)。升級(jí)結(jié)束后,把程序在我手機(jī)上運(yùn)行了一下,發(fā)現(xiàn)昨天的詭異問(wèn)題又出現(xiàn)了。就隔了一個(gè)晚上,怎么就不好使了?誰(shuí)動(dòng)我代碼了?

又跑去同事那借了手機(jī)運(yùn)行,沒(méi)問(wèn)題,在我手機(jī)上再次運(yùn)行,有問(wèn)題。同樣的代碼,設(shè)備一樣,效果卻不一樣。唯一的差異在于系統(tǒng),我是11.3,他是10.3。等等,難道是ios11的適配沒(méi)做好?

翻了翻代碼,果然沒(méi)適配,于是把之前適配ios11的代碼搬過(guò)來(lái),一運(yùn)行,徹底沒(méi)問(wèn)題了。

        self.estimatedRowHeight = 0;
        self.estimatedSectionHeaderHeight = 0;
        self.estimatedSectionFooterHeight = 0;
        //適配ios11自適應(yīng)上導(dǎo)航 安全區(qū)域
        self.separatorStyle = UITableViewCellSeparatorStyleNone;
        SEL selector = NSSelectorFromString(@"setContentInsetAdjustmentBehavior:");
        if ([self respondsToSelector:selector]) {
            IMP imp = [self methodForSelector:selector];
            void (*func)(id, SEL, NSInteger) = (void *)imp;
            func(self, selector, 2);
            
        }

適配ios11的代碼有那么多行,其實(shí)有效果的是estimatedRowHeight這個(gè)。具體請(qǐng)參考鏈接:關(guān)于iOS11中estimatedRowHeight看完你就明白了

4、細(xì)節(jié)整理

確定沒(méi)問(wèn)題了之后,我將代碼整理了一番,整理好之后又測(cè)試了一遍,將延時(shí)時(shí)間調(diào)大了觀(guān)察效果,當(dāng)頁(yè)面停留在加載狀態(tài)的時(shí)候我手指下滑了一下,發(fā)現(xiàn)沒(méi)有翻頁(yè)回彈的效果了,一下子下滑了好幾個(gè)cell。

哦,對(duì)了,我在加載的時(shí)候關(guān)閉了pagingEnabled,得在加載結(jié)束后才會(huì)打開(kāi),可是此時(shí)如果用戶(hù)像我一樣下滑了不就露餡了了么?本著求知心態(tài),我將手機(jī)設(shè)置為3G網(wǎng)絡(luò),快速滑動(dòng)抖音首頁(yè),出現(xiàn)加載后,我又下滑觀(guān)察,抖音是有翻頁(yè)效果的。摸清套路后我開(kāi)始思考我該怎么做?

審查代碼后發(fā)現(xiàn)scrollViewDidScroll中我只判斷了用戶(hù)上拉,沒(méi)有處理下滑,那就再加上下滑判斷。
整理后代碼如下

- (void)scrollViewDidScroll:(UIScrollView *)scrollView{
    [self.tableView jp_scrollViewDidScroll];
    int index= (int)self.tableView.contentOffset.y/kHeight;
    //scroll是與整屏相比的偏移量,肯定是正的
    float scroll = self.tableView.contentOffset.y- index*kHeight;
    //與上一個(gè)滑動(dòng)點(diǎn)比較,區(qū)分上滑還是下滑
    float offset = self.tableView.contentOffset.y- oldOffset.y;
    //記錄當(dāng)前tableView.contentOffset
    oldOffset = self.tableView.contentOffset;
    if (offset>0) {
        //上拉-44是mj_footer的高度,當(dāng)拖拽超過(guò)44的時(shí)候會(huì)觸發(fā)mj
        if (playIndex==_tableView.items.count-1&&scroll>44) {
            if (_tableView.updating==NO) {
                //判斷是否正在刷新,正在刷新就不再進(jìn)行如下設(shè)置,以免重復(fù)加載
                _tableView.updating = YES;
                //進(jìn)到這里說(shuō)明用戶(hù)正在上拉加載,觸發(fā)mj,此時(shí)要關(guān)閉翻頁(yè)功能否則頁(yè)面回彈mj_footer就看不到了,setContentOffset也無(wú)效
                self.tableView.pagingEnabled = NO;
                //給tableView設(shè)置一個(gè)固定的Offset,往上偏移點(diǎn),將footer展示出來(lái),要大于44才會(huì)觸發(fā)footer
                [self.tableView setContentOffset:CGPointMake(0, index*kHeight+50) animated:NO];
                [self.tableView.mj_footer beginRefreshing];
            }
            
        }
    }
    else if (offset<0)
    {
        if (_tableView.updating==YES) {
            //如果用戶(hù)上拉加載時(shí),又進(jìn)行下滑操作,就要打開(kāi)翻頁(yè)功能(可能加載時(shí)間長(zhǎng)用戶(hù)不想等又往上翻之前的cell)-這種情況少見(jiàn)但不排除,不做此操作的話(huà),將請(qǐng)求延時(shí)十秒就會(huì)看到區(qū)別,但一旦用戶(hù)有這種操作就會(huì)有閃屏問(wèn)題,即用戶(hù)在第10個(gè)cell上拉加載了,然后又下滑倒第5個(gè)cell,當(dāng)拿到返回?cái)?shù)據(jù)之后頁(yè)面會(huì)從5自動(dòng)滾動(dòng)到第11個(gè)cell,造成閃屏,但在3G網(wǎng)絡(luò)下經(jīng)測(cè)試抖音也是這樣,故就這樣吧
            self.tableView.pagingEnabled = YES;
        }
        
    }
}

到此就算搞定上拉加載了。

如果你使用時(shí)有什么問(wèn)題,請(qǐng)留言。

感謝NewPan大神的JPVideoPlayer實(shí)現(xiàn)了抖音的自動(dòng)播放

如果對(duì)您有幫助記得點(diǎn)贊收藏哦^?_?^

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

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

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