【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阅读 194,242评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,769评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,484评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,133评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,007评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,080评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,496评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,190评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,464评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,549评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,330评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,205评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,567评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,889评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,160评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,475评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,650评论 2 335

推荐阅读更多精彩内容