-
前言
之前寫(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ì)更新這兩種用法。