Swift Protocal實戰(zhàn)1(Refreshable)

Swift Protocal實戰(zhàn)1(Refreshable)

在app的開發(fā)中,出現(xiàn)最多的一個情況就是顯示一個列表來展示數(shù)據(jù)巩剖,就像刷微博一樣,要能夠上拉加載更多,下拉進行刷新廓握。但在實際開發(fā)過程中,需要考慮的情況會更多嘁酿。我們使用header來表示下拉刷新控件疾棵,使用footer來表示上拉加載控件。

在不考慮緩存的情況下單數(shù)據(jù)源tableView需要考慮以下注意點

  1. 首次進入這個頁面痹仙,沒有數(shù)據(jù)需要進行首次數(shù)據(jù)加載
  2. 首次加載過程中不能顯示footer
  3. 下拉刷新需要用返回的結(jié)果覆蓋數(shù)據(jù)源的數(shù)據(jù)
  4. 下拉刷新后需要還原footer的狀態(tài)(變更為可以加載更多)
  5. 上拉加載成功后需要根據(jù)返回數(shù)據(jù)數(shù)量來判斷是否還有更多數(shù)據(jù),沒有更多數(shù)據(jù)需要修改footer的狀態(tài)為沒有更多數(shù)據(jù)了殉了,并禁止上拉刷新功能开仰。
  6. 首次加載數(shù)據(jù)如果沒有網(wǎng)絡(luò)連接或者加載失敗,需要顯示一個失敗頁面,點擊失敗頁面能夠重新進行網(wǎng)絡(luò)請求獲取數(shù)據(jù)众弓。
  7. 每次進行下拉刷新都需要將當前page設(shè)為1
  8. 每次進行上拉加載前都需要將當前page進行+1操作
  9. 每次上拉加載失敗都需要將當前page進行-1操作恩溅,以還原防止,下次上拉加載page多加了的問題

如果是多數(shù)據(jù)源的tableView則需要考慮的更多

  1. 首先就是不同數(shù)據(jù)源的page記錄谓娃,每個數(shù)據(jù)源都需要對應(yīng)一個自己的page
  2. 每個數(shù)據(jù)源都需要記錄是否還有更多數(shù)據(jù)可供加載
  3. 甚至每個數(shù)據(jù)源擁有各自的沒數(shù)據(jù)的文字提示
  4. 實際項目中脚乡,為了一些效果,還需要記錄當前數(shù)據(jù)源是否處于加載數(shù)據(jù)狀態(tài)滨达,以此來顯示某些加載頁面奶稠。
  5. 可能還會有一個個性化的定制需求

綜上所述,僅僅是一個控制器的數(shù)據(jù)加載邏輯就有這么多捡遍,如果有很多這樣類似的頁面锌订,每次都要考慮這么多問題,難免會有不少疏忽画株,而且要實現(xiàn)這些功能會產(chǎn)生大量的重復(fù)代碼辆飘,這肯定是我們不希望看到的。

分析上述需求我們發(fā)現(xiàn)谓传,實際上通常我們所接觸的tableView大體上也就需要注意這么多問題蜈项,而且為了整個工程的統(tǒng)一性,一般情況所有的處理也是采用同一套邏輯续挟,因此我們完全可以把這些邏輯統(tǒng)一起來紧卒,使用一個Protocol來實現(xiàn)這些邏輯。得益于Swift強大的Protocol Extention 大部分情況我們只需要在合適的關(guān)鍵點調(diào)用幾個方法就可以了庸推,所有的邏輯默認都已經(jīng)實現(xiàn)了常侦。Controller中的代碼更少了,不相關(guān)的邏輯都封裝好了贬媒,邏輯更加簡潔了聋亡。

控制器使用代碼

class PYMyOrderListController: PYBaseViewController, Refreshable {
    internal var refreshStatus: [(page: Int, isLoading: Bool, noMoreData: Bool, noMoreTitle: String)] = [(1,false,false,"沒有更多訂單了")]     // 定義每個數(shù)據(jù)源需要的四個屬性,分別是當前頁碼际乘,是否被正在加載中坡倔,是否沒有更多數(shù)據(jù)可供加載了。沒有數(shù)據(jù)可供加載的footer文字
    internal var currentIndex = 0       // 當前現(xiàn)實的數(shù)據(jù)源索引
    internal var refreshTable: UITableView = UITableView()      // 當前tableView
   
    override func viewDidLoad() {
         view.addSubview(tableView)
         tableView.snp_makeConstraints { (make) in
                     make.left.right.bottom.equalTo(0)
                     make.top.equalTo(segementView.snp_bottom)
             }
         refreshTable = tableView       // 賦值當前tableView
         setupRefreshHeader()           //初始化下拉刷新控件
         setupRefreshFooter()           // 初始化上拉加載控件
     }    
        ///  加載數(shù)據(jù)的方法
        ///
        ///  - parameter isRefresh: 是否是下拉刷新
    func refreshData(isRefresh: Bool) {    
        refreshStatus[currentIndex].isLoading = true        // 修改當前數(shù)據(jù)源的加載狀態(tài)為正在加載
        let indexItem = currentIndex
        let url = URL_OrderList + "/\(type)/\(PageCount)" + "/\(refreshStatus[currentIndex].page).json"
        let request = PYNetWorkTools.GET(url, hudType: .None, failer: { [weak self] (failerTuples) in
            guard self != nil else{ return }
            self?.loadFailer(failerTuples)      // 加載失敗的方法
        }) {[weak self] (response, jsonResult) in
            guard self != nil else{ return }
            self?.tableView.hiddenNoNetPlace()
            let array = PYOrderListModel.modelArray(jsonResult)
            if isRefresh {
                self?.totalArray[indexItem] = array
            } else {
                self?.totalArray[indexItem] += array
            }
            self?.loadSuccess(array.count < PageCount)      // 加載成功的方法脖含,并傳遞一個是否還有更多數(shù)據(jù)的返回值
        }
        if request != nil {
            requests.append(request!)
        }
    }
}    

以上這些代碼就可以實現(xiàn)上述所有的功能罪塔,怎么樣,是不是很有魅力呢养葵?實例中使用了Refreshable協(xié)議征堪,這套協(xié)議可以用在UIViewControllerUITableViewController中,其中的refreshTable就是為了適配UIViewController所增加的一個屬性关拒,否則連這個屬性都不用寫了佃蚜。

代碼中能看到的協(xié)議中定義的內(nèi)容如下

  • 屬性

    • refreshStatus
    • currentInidex
    • refreshTable
  • 方法

    • refreshData(isRefresh: Bool)
    • setupRefreshHeader()
    • setupRefreshFooter()
    • loadFailer(failerTuples: FailerTuples)
    • loadSuccess(noMoreData: Bool?)

讓我們先看看Refreshable這個協(xié)議里是怎么寫的

///  刷新協(xié)議
protocol Refreshable {
    ///  刷新數(shù)據(jù)的方法庸娱,必須實現(xiàn),調(diào)用這個方法來執(zhí)行下拉刷新和上拉加載
    ///
    ///  - parameter isRefresh: 是否是下拉刷新
    func refreshData(isRefresh: Bool)
    
    /// 刷新狀態(tài)的四個參數(shù)谐算,分別是熟尉,當前頁碼,是否正在加載中洲脂,是否沒有更多數(shù)據(jù)了斤儿,沒有更多數(shù)據(jù)的footer顯示文字
    var refreshStatus: [(page: Int, isLoading: Bool, noMoreData: Bool, noMoreTitle: String)] {set get}
    
    /// 當前數(shù)據(jù)源的索引號
    var currentIndex: Int {set get}
    
    /// 需要處理的tableView
    var refreshTable: UITableView {get set}
}

// MARK: - 遵守這個協(xié)議的是控制器
extension Refreshable where Self: UIViewController {

    ///  加載數(shù)據(jù)失敗調(diào)用此方法
    ///
    ///  - parameter failerTuples: 失敗原因
    mutating func loadFailer(failerTuples: (type: NetFailerType, desc: String?)?) {
        refreshStatus[currentIndex].page -= 1
        if refreshStatus[currentIndex].page < 0 {
            refreshStatus[currentIndex].page = 0
        }
        refreshStatus[currentIndex].isLoading = false
        refreshFooter()
        if refreshTable.mj_header != nil {
            refreshTable.mj_header.endRefreshing()
        }
        
        if failerTuples?.type == NetFailerType.NoNet {
            if refreshTable.visibleCells.isEmpty {
                refreshTable.showNoNetPlace({ [weak self] in
                    guard self != nil else { return }
                    self?.refreshData(true)
                    })
            } else {
                showToast(failerTuples?.type.rawValue ?? "")
            }
        }
        refreshTable.reloadData()
    }
    
    ///  加載數(shù)據(jù)成功調(diào)用此方法
    ///
    ///  - parameter noMoreData: 是否沒有更多數(shù)據(jù)了
    mutating func loadSuccess(noMoreData: Bool?) {
        refreshStatus[currentIndex].isLoading = false
        if let noMoreData = noMoreData where refreshTable.mj_footer != nil {
            refreshStatus[currentIndex].noMoreData = noMoreData
            refreshTable.mj_footer.hidden = false
            refreshFooter()
        }
        if refreshTable.mj_header != nil {
            refreshTable.mj_header.endRefreshing()
        }
        refreshTable.reloadData()
    }
    
    ///  初始化下拉刷新控件
    func setupRefreshHeader() {
        let header = MJRefreshNormalHeader {[weak self] () -> Void in
            guard self != nil else { return }
            self?.refreshTable.mj_footer.resetNoMoreData()
            self?.refreshStatus[self!.currentIndex].page = 1
            self?.refreshData(true)
            self?.refreshStatus[self!.currentIndex].isLoading = true
        }
        refreshTable.mj_header = header
    }
    
    ///  初始化上拉加載控件
    func setupRefreshFooter() {
        let footer = MJRefreshBackStateFooter {[weak self] () -> Void in
            guard self != nil else { return }
            self?.refreshStatus[self!.currentIndex].page += 1
            self?.refreshData(false)
            self?.refreshStatus[self!.currentIndex].isLoading = true
        }
        footer.hidden = true
        refreshTable.mj_footer = footer
    }
    
    ///  刷新上拉加載控件,用來重置上拉刷新控件狀態(tài)恐锦,控制能夠刷新以及顯示類型
    func refreshFooter() {
        if refreshTable.mj_footer != nil {
            if refreshStatus[currentIndex].noMoreData {
                refreshTable.mj_footer.endRefreshingWithNoMoreData()
            } else {
                refreshTable.mj_footer.endRefreshing()
            }
        }
    }
}

整個協(xié)議簡潔明了往果,沒有一句廢話。就把眾多需要的功能及注意點都涵蓋了踩蔚。該協(xié)議具有以下特點棚放。

  • 支持UITableViewController以及UIViewcontroller的刷新處理。
  • 支持多數(shù)據(jù)源的切換加載馅闽。

這兩個特性已經(jīng)涵蓋了日常開發(fā)中常見的所有情況飘蚯。當然你也可以只添加上拉加載,不添加下拉刷新功能福也,總之局骤,這些都隨便。

源碼在這

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末暴凑,一起剝皮案震驚了整個濱河市峦甩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌现喳,老刑警劉巖凯傲,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異嗦篱,居然都是意外死亡冰单,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進店門灸促,熙熙樓的掌柜王于貴愁眉苦臉地迎上來诫欠,“玉大人,你說我怎么就攤上這事浴栽』牡穑” “怎么了?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵典鸡,是天一觀的道長被廓。 經(jīng)常有香客問我,道長萝玷,這世上最難降的妖魔是什么伊者? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任英遭,我火速辦了婚禮,結(jié)果婚禮上亦渗,老公的妹妹穿的比我還像新娘。我一直安慰自己汁尺,他們只是感情好法精,可當我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著痴突,像睡著了一般搂蜓。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上辽装,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天帮碰,我揣著相機與錄音,去河邊找鬼拾积。 笑死殉挽,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的拓巧。 我是一名探鬼主播斯碌,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肛度!你這毒婦竟也來了傻唾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤承耿,失蹤者是張志新(化名)和其女友劉穎冠骄,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體加袋,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡凛辣,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了锁荔。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片蟀给。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖阳堕,靈堂內(nèi)的尸體忽然破棺而出跋理,到底是詐尸還是另有隱情,我是刑警寧澤恬总,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布前普,位于F島的核電站,受9級特大地震影響壹堰,放射性物質(zhì)發(fā)生泄漏拭卿。R本人自食惡果不足惜骡湖,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望峻厚。 院中可真熱鬧响蕴,春花似錦、人聲如沸惠桃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辜王。三九已至劈狐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間呐馆,已是汗流浹背肥缔。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留汹来,地道東北人续膳。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像俗慈,于是被迫代替她去往敵國和親姑宽。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,129評論 25 707
  • 發(fā)現(xiàn) 關(guān)注 消息 iOS 第三方庫闺阱、插件炮车、知名博客總結(jié) 作者大灰狼的小綿羊哥哥關(guān)注 2017.06.26 09:4...
    肇東周閱讀 12,103評論 4 62
  • 說起陳繼儒,大家也許不怎么熟悉酣溃,他是明代知名文學(xué)家瘦穆、書畫家,字仲醇赊豌,號眉公扛或、麋公。有《梅花冊》碘饼、《云山卷》熙兔、《陳眉...
    娑婆如斯閱讀 501評論 10 33