【RxSwift系列】RxSwift下如何實(shí)現(xiàn)基于MJRefresh的上下拉刷新?


  • 前言

之前寫(xiě)了一篇如何利用rxswift實(shí)現(xiàn)tableview的文章,那時(shí)候剛接觸rxswift,對(duì)響應(yīng)式編程和mvvm的理解還不是很透徹,直接扒了一篇外網(wǎng)的文章就翻譯了。現(xiàn)在看來(lái),很不適合,其中的內(nèi)容實(shí)用性也很差。今天更這篇利用rxswift實(shí)現(xiàn)上下拉刷新的文章,也會(huì)談到tableview的問(wèn)題。


在rxswift當(dāng)中更新UI而非業(yè)務(wù)相關(guān)的網(wǎng)絡(luò)請(qǐng)求,經(jīng)常會(huì)放在viewModel中實(shí)現(xiàn)。
viewModel的網(wǎng)絡(luò)請(qǐng)求后,控制器需要實(shí)現(xiàn)回調(diào)。如果自己使用通知或者閉包實(shí)現(xiàn)回調(diào)也是可以的。但是使用rxswift可以減少我們的工作量,也比較優(yōu)雅(比較優(yōu)雅這句是我瞎編的 - -#)。
為了以后添加功能方便,以及統(tǒng)一性。所有viewmodel都繼承自baseViewModel。
baseViewModel中可以添加一個(gè)與刷新有關(guān)的變量。

class BaseViewModel: NSObject {
let disposeBag = DisposeBag()
var refreshStatus = Variable.init(RefreshStatus.InvalidData)
}

其中disposeBag是rxswift用來(lái)釋放資源的一個(gè)類(lèi)。建議所有自定義的基礎(chǔ)類(lèi)都添加這個(gè)變量(這里我寫(xiě)的是一個(gè)常量,如果你不太確定是否會(huì)經(jīng)常用到它,可以寫(xiě)個(gè)lazy var形式的變量聲明)。
refreshStatus是一個(gè)variable類(lèi)型。variable類(lèi)型是rxswift當(dāng)中特有的一個(gè)類(lèi)型。它是一個(gè)泛型,它的.value屬性指向的就是它的實(shí)際參數(shù)類(lèi)型。比如在我例子中,variable的實(shí)際參數(shù)類(lèi)型是RefreshStatus,它是一個(gè)枚舉類(lèi)型。

enum RefreshStatus: Int {
case DropDownSuccess // 下拉成功
case PullSuccessHasMoreData // 上拉,還有更多數(shù)據(jù)
case PullSuccessNoMoreData // 上拉,沒(méi)有更多數(shù)據(jù)
case InvalidData // 無(wú)效的數(shù)據(jù)
}

variable類(lèi)型的特點(diǎn)在于,只要改變value的值,就會(huì)發(fā)射改變后的數(shù)據(jù)。如果你對(duì)rxswift不太了解,你只需要知道variable的這個(gè)特性就行了。
這就代表著,只要你在viewModel里面的回調(diào)方法里改變r(jià)efreshStatus的值,它就會(huì)發(fā)射對(duì)應(yīng)的數(shù)據(jù)。這樣在你的控制器中監(jiān)聽(tīng)數(shù)據(jù)的變化,就可以響應(yīng)刷新了。

我在baseViewModel中實(shí)現(xiàn)了三個(gè)方法,用來(lái)處理網(wǎng)絡(luò)請(qǐng)求回調(diào)的普遍操作。

/**
 重寫(xiě)刷新方法,發(fā)射刷新信號(hào)
 */
override func updateData<List>(inout source: [List], list: [List], pullRefresh: Bool) {
    super.updateData(&source, list: list, pullRefresh: pullRefresh)
    // 刷新處理
    if pullRefresh {  // 上拉刷新處理
        self.refreshStatus.value = self.pageModel.hasNext ? .PullSuccessHasMoreData : .PullSuccessNoMoreData
    } else { // 下拉刷新處理
        self.refreshStatus.value = .DropDownSuccess
    }
}

這是我用來(lái)處理分頁(yè)請(qǐng)求回調(diào)成功的方法。其中pullRefresh的布爾值用來(lái)判斷你是上拉還是下拉。true為上拉,false為下拉。
pageModel用來(lái)處理分頁(yè)。其中的hasNext屬性用來(lái)判斷是否還有下一頁(yè)。
這樣,請(qǐng)求成功后,根據(jù)你上下拉的不同,發(fā)射不同的信號(hào),你也可以用來(lái)做不同的處理。

網(wǎng)絡(luò)請(qǐng)求失敗和出錯(cuò)都會(huì)統(tǒng)一調(diào)用另外一個(gè)方法:

 func revertCurrentPageAndRefreshStatus() {
    // 修改刷新view的狀態(tài)
    self.refreshStatus.value = .InvalidData
    // 還原請(qǐng)求頁(yè)
    self.pageModel.currentPage = self.pageModel.currentPage > 1 ? self.pageModel.currentPage - 1 : 1
}

在請(qǐng)求失敗后,把刷新?tīng)顟B(tài)置為此次無(wú)效。把請(qǐng)求分頁(yè)還原到當(dāng)前請(qǐng)求的分頁(yè)。

這樣,在viewModel中的處理我們已經(jīng)處理好了,下一步就是到控制器中處理回調(diào)。
可以聲明一個(gè)TableController,自帶一個(gè)tableview控件。為它設(shè)置好mj_header和mj_footer。這里是對(duì)MJRefresh三房庫(kù)的調(diào)用。如果你不會(huì)使用可以直接到Github上查看原作者給出的用例。
在你的控制器中,可以實(shí)現(xiàn)一個(gè)方法用來(lái)做刷新?tīng)顟B(tài)的處理。

/**
 設(shè)置刷新?tīng)顟B(tài)
 */
func setUpRefreshStatus() {
    tmpViewModel?.refreshStatus.asObservable().bindNext { [unowned self] (status) in
        switch status {
        case .InvalidData:
            self.tableView.endRefreshing()
            return
        case .DropDownSuccess:
            self.tableView.footerResetNoMoreData()
            self.tableView.footerEndRefreshing()
        case .PullSuccessHasMoreData:
            self.tableView.footerEndRefreshing()
        case .PullSuccessNoMoreData:
            self.tableView.footerEndRefreshWithNoMoreData()
        }
        self.tableView.headerEndRefreshing()
        }.addDisposableTo(disposeBag)
}

其中tmpViewModel寫(xiě)成一個(gè)可選類(lèi)型,代表著每個(gè)控制器綁定的viewModel。為什么這里沒(méi)有寫(xiě)viewModel的實(shí)際類(lèi)型,是因?yàn)槊總€(gè)控制器綁定的ViewModel可能是不同的類(lèi)型,這里是針對(duì)baseViewModel的處理。用了?可選類(lèi)型是因?yàn)橐苍S你的控制器并沒(méi)有一個(gè)viewModel。
后面的綁定是rxswift的用法,最后的bindnext閉包是當(dāng)viewModel的refreshStatus改變value后,所做的回調(diào)。
記得最后要添加disposeBag做資源的釋放。這里的disposeBag是控制器的屬性,并不是viewModel的。

記得在控制器加載后,做下面的操作:

    if let vm = self.valueForKey(“viewModel”) as? BaseViewModel {
        tmpViewModel = vm // 利用kvc設(shè)置tmpViewModel,這樣就不需要在每個(gè)子類(lèi)設(shè)置了
    }

這里是用kvc動(dòng)態(tài)查詢(xún)你的控制器是否有viewModel屬性。記得做查詢(xún)不到的操作,不然程序會(huì)crash。

override func valueForUndefinedKey(key: String) -> AnyObject? {
    if key == "viewModel" {
        return nil
    }
        return super.valueForUndefinedKey(key)
}

這樣針對(duì)整個(gè)刷新的操作就結(jié)束了。怎么樣,也不是很難吧。??
上面所做的kvc操作是為了你能夠有一個(gè)基礎(chǔ)類(lèi)設(shè)置上下拉刷新,這樣你自定義的其他子類(lèi)就不需要做其他任何操作,就可以完成上下拉刷新的UI改變。
下面談?wù)則ableview刷新的問(wèn)題。


tableview的刷新也用了同樣的原理。在viewModel中聲明一個(gè)variable類(lèi)型的變量。
它的參數(shù)類(lèi)型,是你實(shí)際請(qǐng)求后的數(shù)據(jù)類(lèi)型。
var dataSource = Variable.init([CoursesModel]())
如果你的頁(yè)面是一個(gè)純列表頁(yè)面,每個(gè)cell長(zhǎng)得都一樣,你可以用這樣的方法,variable的參數(shù)類(lèi)型是一個(gè)數(shù)組。
如果你的頁(yè)面每個(gè)cell都不一樣,你可以直接variable的變量是一個(gè)model。
var trainInfoData = Variable.init(TrainInfoModel())
在網(wǎng)絡(luò)請(qǐng)求成功的回調(diào)里,改變變量的值
self.trainInfoData.value = model
上面的model是你請(qǐng)求回來(lái)的數(shù)據(jù)。
這樣,在控制器里做對(duì)variable的觀察。和上下拉刷新是一樣的。
這里的區(qū)別在于,如果你是用列表形式的,可以直接用rxswift提供的綁定方法:

                   viewModel.dataSource.asObservable().bindTo(tableView.rx_itemsWithCellIdentifier(reuseIdentifier, cellType: ClassesCell.self)) {
        row, model, cell in
        cell.model = model
        }.addDisposableTo(disposeBag) 

它默認(rèn)你的tableview只有一個(gè)分組,你不需要實(shí)現(xiàn)tableview的數(shù)據(jù)源方法。
如果的頁(yè)面不是列表形式的,你可以只做監(jiān)聽(tīng),自己實(shí)現(xiàn)tableview的數(shù)據(jù)源方法(RxDataSource提供了另外的方法,你如果想用使用也可以)。

viewModel.dataSource.asObservable().bindNext { [unowned self] model in self.tableView.reloadData() }.addDisposableTo(disposeBag)

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return 2
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    return 1
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    
    if indexPath.section == 0 {
        let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier0, forIndexPath: indexPath) as! MineFirstCell
        cell.model = viewModel.dataSource.value
        cell.setIndexPath(indexPath.section)
        return cell
    }
    
    let cell = tableView.dequeueReusableCellWithIdentifier(reuseIdentifier1, forIndexPath: indexPath) as! TrainInfoCell
    cell.model = viewModel.dataSource.value
    cell.setIndexPath(indexPath.section)
    return cell
    
}

如果是自己實(shí)現(xiàn)了數(shù)據(jù)源方法,需要在監(jiān)聽(tīng)回調(diào)里刷新tableview,然后傳回來(lái)的model就是請(qǐng)求后拿到的model。
如果是列表的方法,rxswift幫我們做了刷新列表。不需要我們?cè)偈謩?dòng)刷新了。
這就是rxswift的tableview的用法。
其實(shí)使用這個(gè)用法,我們還可以為每一個(gè)控制器添加第一次進(jìn)入請(qǐng)求時(shí)的遮罩view,以及請(qǐng)求失敗或者沒(méi)有數(shù)據(jù)的成errorview,有時(shí)間我會(huì)更新這兩種用法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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