-
前言
因?yàn)楹屯峦蝗粵Q定要在項(xiàng)目里使用MVVM架構(gòu) + 響應(yīng)式編程 + Swift,最近一直在擼RxSwift。由于沒(méi)有很完善的中文教程和文檔,所以學(xué)習(xí)的過(guò)程中遇到了很多坑,比如一個(gè)簡(jiǎn)單的實(shí)現(xiàn)UITableView就搞了好久...
于是決定把自己遇到的坑都記錄下來(lái),順便翻譯一些有用的英文材料,給后來(lái)踩坑的人留下一些經(jīng)驗(yàn)。
今天要介紹的就是UITableView在RxSwift中的使用方法,我也是Google了好些資料,最后找到了這篇《Implement a UITableView in RxSwift》博文,按照上面的例子實(shí)現(xiàn)了UITableView。今天要講的UITableView的用法也是主要翻譯這篇博文,加上自己的一些改良。
長(zhǎng)話短說(shuō),讓我們開(kāi)始吧。
-
RxSwift是什么
RxSwift是一個(gè)針對(duì)于Swift語(yǔ)言的響應(yīng)式編程框架,旨在使異步操作和事件/數(shù)據(jù)流的實(shí)現(xiàn)變的簡(jiǎn)單。這里不做過(guò)多的介紹,直接進(jìn)入教程。
-
示例
使用Xcode新建一個(gè)工程,并把語(yǔ)言選擇為Swift。然后添加RxSwift框架到你的工程里。你可以使用CocoaPods來(lái)管理三方庫(kù)。
你的Podflie文件看起來(lái)應(yīng)該是這樣的:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, ‘8.1’
use_frameworks!
target 'RxTableView' do
pod 'RxSwift'
pod 'RxCocoa'
end
注意!確保你安裝了RxDataSources這個(gè)三方庫(kù)!
RxDataSources是使用RxSwift對(duì)UITableView和UICollectionView的數(shù)據(jù)源做了一層包裝。作者一開(kāi)始在嘗試的時(shí)候就沒(méi)有包含這個(gè)庫(kù),結(jié)果一啟動(dòng)就Crash,一啟動(dòng)就Crash,無(wú)限循環(huán)...
最可惡的是官方給的Example里面沒(méi)有用Pods加入這個(gè)庫(kù),而是手動(dòng)放到工程里的,沒(méi)仔細(xì)看目錄結(jié)構(gòu)之前都不知道有這個(gè)鬼東西...
所以,實(shí)際上,你的Podfile文件里還要加上RxDataSources,這樣你的Podflie看起來(lái)應(yīng)該是這樣的:
source 'https://github.com/CocoaPods/Specs.git'
platform :ios, ‘8.1’
use_frameworks!
target 'RxTableView' do
pod 'RxSwift'
pod 'RxCocoa'
pod 'RxDataSources'
end
接著,新建一個(gè)ViewController,并給它添加一個(gè)UITableView,你的代碼看起來(lái)應(yīng)該是這樣的:
import UIKit
import RxCocoa
import RxSwift
import RxDataSources
class RxTableViewController: UIViewController {
let tableView: UITableView = UITableView(frame: UIScreen.mainScreen().bounds, style: .Plain)
let reuseIdentifier = "\(TableViewCell.self)"
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.registerClass(TableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
}
}
很好,現(xiàn)在我們要開(kāi)始實(shí)現(xiàn) UITableViewDelegate和UITableViewDataSource方法了,對(duì)吧?
哈,其實(shí)不需要這樣做。我們使用了響應(yīng)式的方法來(lái)編程,就不在需要寫(xiě)這些數(shù)據(jù)源和代理方法了?,F(xiàn)在你要確保你的控制器里importRxSwift和RxDataSource兩個(gè)模塊就可以了,我們使用RxSwift來(lái)配置我們的TableView。
細(xì)心的你應(yīng)該會(huì)發(fā)現(xiàn)作者自定義了一個(gè)TableviewCell,這個(gè)后面再提。
現(xiàn)在我們創(chuàng)建一個(gè)Model,來(lái)代表簡(jiǎn)書(shū)的用戶(hù)對(duì)象,它有關(guān)注、粉絲、昵稱(chēng)三個(gè)屬性。
import Foundation
struct User {
let followersCount: Int
let followingCount: Int
let screenName: String
}
現(xiàn)在你會(huì)不會(huì)感到困惑,為什么我們的Model使用了一個(gè)Struct而不是一個(gè)Class呢?
作者和你一樣困惑。呃... 作者翻譯的這篇博文的原作者就是這樣寫(xiě)的,而且原作者創(chuàng)建的ViewModel文件還包含了UIKit模塊,實(shí)際上MVVM模式下ViewModel是最好不要包含UI相關(guān)的元素的。不過(guò)我們先不要在意這些細(xì)節(jié),畢竟我們這篇的目的是研究如何使用UITableView不是。
回到我們的ViewController文件,聲明這樣一個(gè)屬性:
let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, User>>()
RxDataSources類(lèi)指定了我們的數(shù)據(jù)源包括哪些內(nèi)容。SectionModel帶有一個(gè)String作為section的名字,User類(lèi)作為item的類(lèi)型。如果你不太明白的話,可以按住?并點(diǎn)擊對(duì)象的聲明來(lái)查看它內(nèi)部的實(shí)現(xiàn)是怎樣的。
現(xiàn)在我們創(chuàng)建一個(gè)ViewModel類(lèi)用來(lái)傳遞我們的數(shù)據(jù)源。出于給控制器減負(fù)的目的,我們要避免直接在控制器里處理Model類(lèi)。你的ViewModel看上去應(yīng)該是這樣的:
import Foundation
import RxSwift
import RxDataSources
class ViewModel: NSObject {
}
讓我們?yōu)閂iewModel類(lèi)添加一個(gè)獲取數(shù)據(jù)的功能。在你的真實(shí)的應(yīng)用中,你的數(shù)據(jù)更可能是通過(guò)網(wǎng)絡(luò)請(qǐng)求解析JSON數(shù)據(jù)而獲得來(lái)的,在我們的例子中,我們先寫(xiě)一段假的數(shù)據(jù)。
ViewModel的方法看起來(lái)應(yīng)該是這樣的:
import Foundation
import RxSwift
import RxDataSources
class ViewModel: NSObject {
func getUsers() -> Observable<[SectionModel<String, User>]> {
return Observable.create { (observer) -> Disposable in
let users = [User(followersCount: 19_901_990, followingCount: 1990, screenName: "Marco Sun"),
User(followersCount: 19_890_000, followingCount: 1989, screenName: "Taylor Swift"),
User(followersCount: 250_000, followingCount: 25, screenName: "Rihanna"),
User(followersCount: 13_000_000_000, followingCount: 13, screenName: "Jolin Tsai"),
User(followersCount: 25_000_000, followingCount: 25, screenName: "Adele")]
let section = [SectionModel(model: "", items: users)]
observer.onNext(section)
observer.onCompleted()
return AnonymousDisposable{}
}
}
}
一個(gè)Observable是響應(yīng)式編程里最重要也是最基本的概念。它是一組序列的值。這就是為什么異步操作來(lái)獲取你的數(shù)據(jù)如此簡(jiǎn)單的原因。你可以連接多個(gè)Observable,然后等他們?nèi)客瓿珊笤偎⑿聰?shù)據(jù)(看不懂這段的要回去再研究下RxSwift的幾個(gè)基礎(chǔ)概念)。
我們剛剛定義的Observable是一個(gè)數(shù)組里面裝了SectionModel對(duì)象,SectionModel里面包含了String型的標(biāo)題和User型的item。還記得這個(gè)類(lèi)嗎?它實(shí)際上是我們的TableView的row的數(shù)據(jù)的容器。我們插入了五個(gè)假數(shù)據(jù)在數(shù)組里,并且創(chuàng)建了一個(gè)section。section的名字用了一個(gè)空的字符串。如果你想給section的區(qū)頭加上title,你可以填充這個(gè)字符串并且在控制器里實(shí)現(xiàn)titleForHeaderInSection的方法。
之后我們告訴Observable我們的序列完成了,通過(guò)調(diào)用onCompleted()方法來(lái)編譯我們的功能。返回AnonymousDisposable()來(lái)確保在函數(shù)返回后資源可以得到釋放和清理。假如你用了網(wǎng)絡(luò)請(qǐng)求,你可以在AnonymousDisposable()方法的閉包里取消所有的等待請(qǐng)求。關(guān)于事件序列和Disposable()在Getting Started guide里有很好的解釋。
寫(xiě)好了ViewModel的邏輯之后,我們回到控制器文件然后把數(shù)據(jù)源串起來(lái)。首先,先創(chuàng)建兩個(gè)常量ViewModel和DisposeBag。DisposeBag是在控制器銷(xiāo)毀后來(lái)控制釋放資源的。
let viewModel = ViewModel()
let disposeBag = DisposeBag()
在viewDidLoad()方法里,我們配置UItableViewCell然后綁定ViewModel給TableView的數(shù)據(jù)源。
override func viewDidLoad() {
super.viewDidLoad()
view.addSubview(tableView)
tableView.registerClass(TableViewCell.self, forCellReuseIdentifier: reuseIdentifier)
dataSource.configureCell = {
_, tableView, indexPath, user in
let cell = tableView.dequeueReusableCellWithIdentifier(self.reuseIdentifier, forIndexPath: indexPath) as! TableViewCell
cell.tag = indexPath.row
cell.user = user
return cell
}
viewModel.getUsers()
.bindTo(tableView.rx_itemsWithDataSource(dataSource))
.addDisposableTo(disposeBag)
}
這里我們使用了自定義的TableViewCell類(lèi),并給它設(shè)置了一個(gè)user屬性,TableViewCell類(lèi)的內(nèi)部應(yīng)該是這樣的:
import UIKit
class TableViewCell: UITableViewCell {
var user: User? {
willSet {
let string = "\(newValue!.screenName)在簡(jiǎn)書(shū)上關(guān)注了\(newValue!.followingCount)個(gè)用戶(hù),并且被\(newValue!.followersCount)個(gè)用戶(hù)關(guān)注了。"
backgroundColor = tag % 2 == 0 ? UIColor.lightGrayColor() : UIColor.whiteColor()
textLabel?.text = string
textLabel?.numberOfLines = 0
}
}
}
我們重寫(xiě)了cell的willSet方法來(lái)利用數(shù)據(jù)做UI展示。
在上面的viewDidLoad()方法中我們最后還使用了Observable的getUser方法來(lái)返回?cái)?shù)據(jù)源。viewModel會(huì)返回SectionModel對(duì)象,tableview會(huì)自動(dòng)的展示數(shù)據(jù),真棒!~
在手機(jī)上展示的真實(shí)頁(yè)面是這樣的:

RxDataSources的功能是很強(qiáng)大的。除了使用RxTableViewSectionedReloadDataSource我們還可以使用RxTableViewSectionedAnimatedDataSource來(lái)進(jìn)行動(dòng)畫(huà)操作,它對(duì)UICollectionView也有很好的支持。
我們可以擴(kuò)展這個(gè)例子讓它有更多的功能,這個(gè)項(xiàng)目的Demo放在了Github上,如果你需要可以直接下載它來(lái)使用。