本文為投稿文章,作者:馬克叔_Marco(簡書)
前言
因為和同事突然決定要在項目裡使用MVVM架構 + 響應式編程 + Swift,最近一直在撸RxSwift。由於沒有很完善的中文教程和文檔,所以學習的過程中遇到了很多坑,比如一個簡單的實現UITableView就搞了好久...
於是決定把自己遇到的坑都記錄下來,順便翻譯一些有用的英文材料,給後來踩坑的人留下一些經驗。
今天要介紹的就是UITableView在RxSwift中的使用方法,我也是Google了好些資料,最後找到了這篇《Implement a UITableView in RxSwift》博文,按照上面的例子實現了UITableView。今天要講的UITableView的用法也是主要翻譯這篇博文,加上自己的一些改良。
長話短說,讓我們開始吧。
RxSwift是什麼
RxSwift是一個針對於Swift語言的響應式編程框架,旨在使異步操作和事件/數據流的實現變的簡單。這裡不做過多的介紹,直接進入教程。
示例
使用Xcode新建一個工程,並把語言選擇為Swift。然後添加RxSwift框架到你的工程裡。你可以使用CocoaPods來管理三方庫。
你的Podflie文件看起來應該是這樣的:
source 'https://github.com/CocoaPods/Specs.git' platform :ios, ‘8.1’ use_frameworks! target 'RxTableView' do pod 'RxSwift' pod 'RxCocoa' end
注意!確保你安裝了RxDataSources這個三方庫!
RxDataSources是使用RxSwift對UITableView和UICollectionView的數據源做了一層包裝。作者一開始在嘗試的時候就沒有包含這個庫,結果一啟動就Crash,一啟動就Crash,無限循環...最可惡的是官方給的Example裡面沒有用Pods加入這個庫,而是手動放到工程裡的,沒仔細看目錄結構之前都不知道有這個鬼東西...所以,實際上,你的Podfile文件裡還要加上RxDataSources,這樣你的
Podflie看起來應該是這樣的:
source 'https://github.com/CocoaPods/Specs.git' platform :ios, ‘8.1’ use_frameworks! target 'RxTableView' do pod 'RxSwift' pod 'RxCocoa' pod 'RxDataSources' end
接著,新建一個ViewController,並給它添加一個UITableView,你的代碼看起來應該是這樣的:
import UIKitimport RxCocoaimport RxSwiftimport RxDataSourcesclass 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) } }
很好,現在我們要開始實現 UITableViewDelegate和UITableViewDataSource方法了,對吧?
哈,其實不需要這樣做。我們使用了響應式的方法來編程,就不在需要寫這些數據源和代理方法了。現在你要確保你的控制器裡importRxSwift和RxDataSource兩個模塊就可以了,我們使用RxSwift來配置我們的TableView。
細心的你應該會發現作者自定義了一個TableviewCell,這個後面再提。
現在我們創建一個Model,來代表簡書的用戶對象,它有關注、粉絲、昵稱三個屬性。
import Foundationstruct User { let followersCount: Int let followingCount: Int let screenName: String }
現在你會不會感到困惑,為什麼我們的Model使用了一個Struct而不是一個Class呢?
作者和你一樣困惑。呃... 作者翻譯的這篇博文的原作者就是這樣寫的,而且原作者創建的ViewModel文件還包含了UIKit模塊,實際上MVVM模式下ViewModel是最好不要包含UI相關的元素的。不過我們先不要在意這些細節,畢竟我們這篇的目的是研究如何使用UITableView不是。
回到我們的ViewController文件,聲明這樣一個屬性:
let dataSource = RxTableViewSectionedReloadDataSource()
RxDataSources類指定了我們的數據源包括哪些內容。SectionModel帶有一個String作為section的名字,User類作為item的類型。如果你不太明白的話,可以按住並點擊對象的聲明來查看它內部的實現是怎樣的。
現在我們創建一個ViewModel類用來傳遞我們的數據源。出於解耦的目的,我們要避免直接在控制器裡處理Model類。你的ViewModel
看上去應該是這樣的:
import Foundationimport RxSwiftimport RxDataSourcesclass ViewModel: NSObject { }
讓我們為ViewModel類添加一個獲取數據的功能。在你的真實的應用中,你的數據更可能是通過網絡請求解析JSON數據而獲得來的,在我們的例子中,我們先寫一段假的數據。
ViewModel的方法看起來應該是這樣的:
import Foundation import RxSwift import RxDataSourcesclass ViewModel: NSObject { func getUsers() -> Observable { 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{} } } }
一個Observable是響應式編程裡最重要也是最基本的概念。它是一組序列的值。這就是為什麼異步操作來獲取你的數據如此簡單的原因。你可以連接多個Observable,然後等他們全部完成後再刷新數據(看不懂這段的要回去再研究下RxSwift的幾個基礎概念)。
我們剛剛定義的Observable是一個數組裡面裝了SectionModel對象,SectionModel裡面包含了String型的標題和User型的item。還記得這個類嗎?它實際上是我們的TableView的row的數據的容器。我們插入了五個假數據在數組裡,並且創建了一個section。section的名字用了一個空的字符串。如果你想給section的區頭加上title,你可以填充這個字符串並且在控制器裡實現titleForHeaderInSection的方法。
之後我們告訴Observable我們的序列完成了,通過調用onCompleted()方法來編譯我們的功能。返回AnonymousDisposable()來確保在函數返回後資源可以得到釋放和清理。假如你用了網絡請求,你可以在AnonymousDisposable()方法的閉包裡取消所有的等待請求。關於事件序列和Disposable()在Getting Started guide裡有很好的解釋。
寫好了ViewModel的邏輯之後,我們回到控制器文件然後把數據源串起來。首先,先創建兩個常量ViewModel和DisposeBag。DisposeBag是在控制器銷毀後來控制釋放資源的。
let dataSource = RxTableViewSectionedReloadDataSource()
在viewDidLoad()方法裡,我們配置UItableViewCell然後綁定ViewModel給TableView的數據源。
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類,並給它設置了一個user屬性,TableViewCell類的內部應該是這樣的:
import UIKitclass TableViewCell: UITableViewCell { var user: User{ willSet { let string = "(newValue!.screenName)在簡書上關注了(newValue!.followingCount)個用戶,並且被(newValue!.followersCount)個用戶關注了。" backgroundColor = tag % 2 == 0 ? UIColor.lightGrayColor() : UIColor.whiteColor() textLabel.text = string textLabel.numberOfLines = 0 } } }
我們重寫了cell的willSet方法來利用數據做UI展示。
在上面的viewDidLoad()方法中我們最後還使用了Observable的getUser方法來返回數據源。viewModel會返回SectionModel對象,tableview會自動的展示數據,真棒!~
在手機上展示的真實頁面是這樣的:
RxDataSources的功能是很強大的。除了使用RxTableViewSectionedReloadDataSource我們還可以使用RxTableViewSectionedAnimatedDataSource來進行動畫操作,它對UICollectionView也有很好的支持。
我們可以擴展這個例子讓它有更多的功能,這個項目的Demo放在了Github上,如果你需要可以直接下載它來使用。