【RxSwift系列】用RxSwift實現一個UITableView(一)


  • 前言

因為和同事突然決定要在項目里使用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 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)

    }
}

很好,現在我們要開始實現 UITableViewDelegate和UITableViewDataSource方法了,對吧?
哈,其實不需要這樣做。我們使用了響應式的方法來編程,就不在需要寫這些數據源和代理方法了。現在你要確保你的控制器里importRxSwiftRxDataSource兩個模塊就可以了,我們使用RxSwift來配置我們的TableView。

細心的你應該會發現作者自定義了一個TableviewCell,這個后面再提。

現在我們創建一個Model,來代表簡書的用戶對象,它有關注、粉絲、昵稱三個屬性。

import Foundation

struct User {
    let followersCount: Int
    let followingCount: Int
    let screenName: String
}

現在你會不會感到困惑,為什么我們的Model使用了一個Struct而不是一個Class呢?
作者和你一樣困惑。呃... 作者翻譯的這篇博文的原作者就是這樣寫的,而且原作者創建的ViewModel文件還包含了UIKit模塊,實際上MVVM模式下ViewModel是最好不要包含UI相關的元素的。不過我們先不要在意這些細節,畢竟我們這篇的目的是研究如何使用UITableView不是。

回到我們的ViewController文件,聲明這樣一個屬性:

let dataSource = RxTableViewSectionedReloadDataSource<SectionModel<String, User>>()

RxDataSources類指定了我們的數據源包括哪些內容。SectionModel帶有一個String作為section的名字,User類作為item的類型。如果你不太明白的話,可以按住?并點擊對象的聲明來查看它內部的實現是怎樣的。

現在我們創建一個ViewModel類用來傳遞我們的數據源。出于給控制器減負的目的,我們要避免直接在控制器里處理Model類。你的ViewModel看上去應該是這樣的:

import Foundation
import RxSwift
import RxDataSources

class ViewModel: NSObject {

}

讓我們為ViewModel類添加一個獲取數據的功能。在你的真實的應用中,你的數據更可能是通過網絡請求解析JSON數據而獲得來的,在我們的例子中,我們先寫一段假的數據。

ViewModel的方法看起來應該是這樣的:

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{}
    }
}

}

一個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的邏輯之后,我們回到控制器文件然后把數據源串起來。首先,先創建兩個常量ViewModelDisposeBagDisposeBag是在控制器銷毀后來控制釋放資源的。

let viewModel = ViewModel()
let disposeBag = DisposeBag()

viewDidLoad()方法里,我們配置UItableViewCell然后綁定ViewModelTableView的數據源。

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 UIKit

class 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()方法中我們最后還使用了ObservablegetUser方法來返回數據源。viewModel會返回SectionModel對象,tableview會自動的展示數據,真棒!~


在手機上展示的真實頁面是這樣的:

Simulator Screen Shot 2016年4月28日 17.10.58.png

RxDataSources的功能是很強大的。除了使用RxTableViewSectionedReloadDataSource我們還可以使用RxTableViewSectionedAnimatedDataSource來進行動畫操作,它對UICollectionView也有很好的支持。

我們可以擴展這個例子讓它有更多的功能,這個項目的Demo放在了Github上,如果你需要可以直接下載它來使用。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,501評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,673評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,610評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,939評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,668評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,004評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,001評論 3 449
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,173評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,705評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,426評論 3 359
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,656評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,139評論 5 364
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,833評論 3 350
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,247評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,580評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,371評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,621評論 2 380

推薦閱讀更多精彩內容