iOS實現無感知上拉加載更多功能的思路與方法

什麼是無感知上拉加載更多

什麼是無感知,這個這樣理解:在網絡情況正常的情況下,用戶對列表進行連續的上拉時,該列表可以無卡頓不停再見新的數據。

如果要體驗話,Web端很多已經做到瞭,比如掘金的首頁,還有比如i掘金iOS的App,列表都是無感知的。

說來慚愧,寫瞭這久的代碼,還真的沒有認真思考這個功能怎麼實現。

如何實現無感知上拉加載更多

我在看見這位網友留言的時候,就開始思考瞭。

在我看來,有下面幾個著手點:

  • 列表滑動時候的是如何知道具體滑動的位置以觸發接口請求,添加更多數據?
  • 從UIScrollView的代理回調中去找和scrollView的位置(contentOffset)大小(contentSize)關系密切的回調。
  • 網絡上有沒有比較成熟的思路?

順著這條線,我先跑去看瞭UIScrollViewDelegate的源碼:

public protocol UIScrollViewDelegate : NSObjectProtocol {

    
    @available(iOS 2.0, *)
    optional func scrollViewDidScroll(_ scrollView: UIScrollView) // any offset changes

    @available(iOS 3.2, *)
    optional func scrollViewDidZoom(_ scrollView: UIScrollView) // any zoom scale changes

    .
    .
    .
    .
    .
    .
    /// 代碼很多,這裡就不放上來,給大傢壓力瞭。
}

直接上結論吧:看瞭一圈,反正沒有和contentSize或者位置相關的回調代理。scrollViewDidScroll這個回調裡面雖然可以知道scrollView,但是對於我們需要的信息還不夠具體。

思考:既然UIScrollViewDelegate的代理沒有現成的代理回調,自己使用KVO去監聽試試?

網上的思路(一)

就在我思考的同時,我也在網絡上需求實現這個功能的答案,讓後看到這樣一個思路:

實現方法很簡單,需要用到tableView的一個代理方法,就可輕松實現。- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath就是這個方法,自定義顯示cell。這個方法不太常用。但是這個方法可在每個cell將要第一次出現的時候觸發。然後我們可設置當前頁面第幾個cell將要出現時,觸發請求加載更多數據。

我看瞭之後,心想著,多寫一個TableView的代理,總比寫KVO的代碼少,先試試再說,於是代碼擼起:

extension SwiftCoinRankListController: UITableViewDelegate {
    
    func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        let row = indexPath.row
        let distance = dataSource.count - 25
        print("row: \(row), distance:\(distance)  ")
        if row == distance {
            loadMore()
        }
    }
}

本代碼可以在開源項目中的SwiftCoinRankListController.swift文件查看具體的邏輯,其主要就是通過cell顯示的個數去提前請求加載數據,然後我們看看效果:

Gif可能看起來還好,我說我調試的感受:雖然做到瞭上拉無感知,但是當手滑的速度比較快的時候,到底瞭新的數據沒有回來,就會在底部等一段時間。

雖然功能達到瞭,但是感受卻不理想,果然還是監聽的細膩程度不夠。

網上的思路(二)

然後在繼續的搜索中,我看到瞭另外一個方案:

很多時候我們上拉刷新需要提前加載新數據,這時候利用MJRefreshAutoFooter的屬性triggerAutomaticallyRefreshPercent就可以實現,該屬性triggerAutomaticallyRefreshPercent默認值為1,然後改成0的話劃到底部就會自動刷新,改成-1的話,在快劃到底部44px的時候就會自動刷新。

MJRefresh?使用MJRefreshAutoFooter,這個簡單,我直接把基類的footer給替換掉就可以瞭,本代碼可以在開源項目中的BaseTableViewController.swift文件查看:

/// 設置尾部刷新控件,更新為無感知加載更多
let footer = MJRefreshAutoFooter()
footer.triggerAutomaticallyRefreshPercent = -1
tableView.mj_footer = footer

再來看看效果:

直接說感受:

代碼改動性少,編寫簡單,達到預期效果,爽歪歪。比的方案一更絲滑,體驗好。

到此,功能就實現,難道就完瞭?

當然,不會,我們去看看源碼吧。

MJRefresh代碼的追根朔源

首先我們看看MJRefreshAutoFooter.h文件:

這裡有個專門的屬性triggerAutomaticallyRefreshPercent去做自動刷新,那麼我們去MJRefreshAutoFooter.m中去看看吧:

註意看看喔,這個.m文件有一個- (void)scrollViewContentOffsetDidChange:(NSDictionary *)change方法,並且還調用瞭super,從這個方法名中我們可以明顯的得到當scrollView的contentOffset變化的時候進行回調的監聽。,我們順藤摸瓜,看看super是什麼,會不會有新的發現:

稍微跟著一下源代碼,MJRefreshAutoFooter的繼承關系如下:

MJRefreshAutoFooter => MJRefreshFooter => MJRefreshComponent

所以這個super的調用我們就去MJRefreshComponent.m裡面去看看吧:

通過上面的截圖我們可以得到下面的一些信息與結論:

  • MJRefreshComponent是通過KVO去監聽scrollView的contentOffset變化,思路上比較一致。
  • 該類並沒有實現其具體方法,而是將其交由其子類去實現,這一點通過看MJRefreshComponent.h的註釋可以得到:

  • MJRefreshComponent從本質上更像虛基類。

總結

如果不是網友提出這個問題,我可能都不會太仔細的去研究這個功能,也許繼續普普通通的使用一般的上拉加載更多就夠瞭。

這次的實踐,其實是從思路到尋找方法,最後再到源碼閱讀的。

思路也許不困難,但是真正一點點實現並完善功能,每一步都並不容易,這次我也僅僅是繼續使用瞭MJRefresh這個輪子。

到此這篇關於iOS實現無感知上拉加載更多功能的思路與方法的文章就介紹到這瞭,更多相關iOS上拉加載更多內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

參考文章

  • iOS 關於列表上拉(平滑加載數據)自動加載數據的問題
  • MJRefresh小技巧(上拉提前刷新)

推薦閱讀: