分頁加載的列表實現(xiàn)方案

分頁列表是開發(fā)過程中最常見的的需求,雖然簡單,但是還是有一些點值得總結(jié)。之前也沒有過多思考過這個需求,每次都是想到那寫到哪,結(jié)果就是經(jīng)常會犯之前翻過的錯誤,所以決定總結(jié)出一個模版出來。

值得注意的點

  • 兩個動作:下拉刷新,上拉加載更多。
    下拉刷新需要注意,刷新前不能清空數(shù)據(jù),而是要等刷新的數(shù)據(jù)返回之后去替換原來的就數(shù)據(jù)。否則當(dāng)刷新失敗,刷新前的數(shù)據(jù)又被清空,就會導(dǎo)致頁面沒有數(shù)據(jù)。
    上拉加載更多要注意當(dāng)沒有更多數(shù)據(jù)時,我們應(yīng)該把刷新狀態(tài)設(shè)置為沒有更多數(shù)據(jù),可以避免無效刷新。
  • 刷新過程
    控制器需要感知到刷新狀態(tài)(閑置/刷新中/刷新結(jié)束)的變化,以展示合適的UI。
模版
模版.png
  • 控制器/視圖
    業(yè)務(wù)的載體,給用戶提供服務(wù)。
    lazy var manager = ListManager()
    
    lazy var refreshHeader: MJRefreshNormalHeader = {
        let header = MJRefreshNormalHeader { [weak self] in
            self?.manager.reload()
        }
        return header
    }()
    
    lazy var refreshFooter: MJRefreshAutoStateFooter = {
        let footer = MJRefreshAutoStateFooter { [weak self] in
            self?.manager.loadNextPage()
        }
        return footer
    }()
    
   private func handleState(_ state: ListManager.State) {
        switch state {
        case .idle:
            refreshHeader.endRefreshing()
            if manager.isExhausted {
                refreshFooter.endRefreshingWithNoMoreData()
            } else {
                refreshFooter.endRefreshing()
            }
            
        case .loading:
            break
            
        case .loaded(let error):
            tableView.reloadData()
            print(error?.localizedDescription ?? "")
        }
    }
  • Manager
    封裝核心數(shù)據(jù)邏輯管理的類,是業(yè)務(wù)的骨架,給控制器提供服務(wù)。
    這里有下拉刷新和下拉加載兩個動作可以引起頁面變化,但是如果我們從數(shù)據(jù)層面來看的話,其實是這兩個動作引起了列表數(shù)據(jù)變化,數(shù)據(jù)變化引起了頁面變化。所以其實控制器只關(guān)心數(shù)據(jù)和獲取數(shù)據(jù)的進度狀態(tài)。所以我引入了一個Manager去處理這中間的轉(zhuǎn)換。Manager提供reload、loadNextPage接口,然后輸出列表數(shù)據(jù)和刷新狀態(tài)。


    Controller & Manager
class ListManager {
    
    /// 刷新狀態(tài)
    enum State {
        /// 閑置
        case idle
        
        /// 加載中
        case loading
        
        /// 加載結(jié)束(error為nil時表示刷新成功)
        case loaded(Error?)
    }
    
    /// 是否還有數(shù)據(jù)
    var isExhausted: Bool = false
    
    /// 列表數(shù)據(jù)
    private(set) var list = [ListModel]()
    
    /// 狀態(tài)
    private(set) var state: State = .idle {
        didSet {
            statehandler?(state)
        }
    }
    
    /// 狀態(tài)閉包(用于通知控制器獲取數(shù)據(jù)的狀態(tài))
    var statehandler: ((_ state: State) -> Void)?
    
    /// 下拉刷新調(diào)用的方法
    func reload() {
        loadData(offset: 0) { [weak self] items in
            self?.list = items
        }
    }
    
    /// 上拉加載下一頁調(diào)用的方法
    func loadNextPage() {
        loadData(offset: list.count) { [weak self] items in
            self?.list.append(contentsOf: items)
        }
    }
    
    /// 獲取數(shù)據(jù),處理狀態(tài),處理isExhausted
    private func loadData(offset: Int, onSuccess: ((_ list: [ListModel]) -> Void)?) {
        state = .loading
        ListSourceService.loadListData(offset: offset, limit: ListSourceService.limit) { [weak self] list in
            guard let self = self else {
                return
            }
            onSuccess?(list)
            self.isExhausted = list.count < ListSourceService.limit
            self.state = .loaded(nil)
            self.state = .idle
        } onError: { [weak self] error in
            self?.state = .loaded(error)
            self?.state = .idle
        }
    }
}
  • Service
    數(shù)據(jù)來源,給Manger提供服務(wù)。
struct ListSourceService {
    /// 每頁數(shù)量
    static var limit = 10
    
    static let totolCount = 55
    
    /// 獲取數(shù)據(jù)方法(這里用延時模擬獲取數(shù)據(jù))
    static func loadListData(offset: Int, limit: Int, onSuccess: @escaping ((_ list: [ListModel]) -> Void), onError: ((_ error: Error) -> Void)) {
        DispatchQueue.main.asyncAfter(deadline: .now()+0.5) {
            var items = [ListModel]()
            let count = Self.totolCount - offset
            guard count > 0 else {
                onSuccess(items)
                return
            }
            let remain = min(Self.limit, count)
            for i in 0..<remain {
                let model = ListModel(name: "第\(i+offset+1)條數(shù)據(jù)")
                items.append(model)
            }
            onSuccess(items)
        }
    }
}
總結(jié)

這樣做可以達(dá)到數(shù)據(jù)邏輯和UI分離、數(shù)據(jù)驅(qū)動UI的效果。數(shù)據(jù)邏輯和UI分離使得對其任一部分的修改都不會影響另一部分;數(shù)據(jù)驅(qū)動UI使得代碼可讀性、維護性會更高。

?著作權(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)容

  • Mvp與Mvvm已經(jīng)出現(xiàn)很久了,但是還有很多開發(fā)者沒有運用到自己的項目中。正好最近工作不是很忙,這兩天空余時間就寫...
    MjCodeTinker閱讀 2,498評論 0 17
  • 我是黑夜里大雨紛飛的人啊 1 “又到一年六月,有人笑有人哭,有人歡樂有人憂愁,有人驚喜有人失落,有的覺得收獲滿滿有...
    陌忘宇閱讀 8,889評論 28 54
  • 首先介紹下自己的背景: 我11年左右入市到現(xiàn)在,也差不多有4年時間,看過一些關(guān)于股票投資的書籍,對于巴菲特等股神的...
    瞎投資閱讀 5,966評論 3 8
  • ![Flask](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAW...
    極客學(xué)院Wiki閱讀 7,850評論 0 3
  • 不知不覺易趣客已經(jīng)在路上走了快一年了,感覺也該讓更多朋友認(rèn)識知道易趣客,所以就謝了這篇簡介,已做創(chuàng)業(yè)記事。 易趣客...
    Physher閱讀 3,838評論 1 2

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