RxSwift介绍(五)——TableView的应用
这次打算单独将 tableView 在 RxSwift 框架中的使用整理成一篇文章。tableView 在日常开发中是接触到最多的UI控件之一,在 RxSwift 框架中也帮我们封装好了关于 tableview 的使用方法。自从接触了 RxSwift 的框架,关于 tableview 基本就不用再繁琐地去实现系统提供的各种代理方法,几行代码搞定关于 tableview 的一切。顺便吐槽一下编译器RxSwift代码提示总是无法及时显示,甚至得手写方法名和参数,尤其是 tableview ...
TableView 基本应用
首先,为了更好地展示 RxSwift 在 TableView 方面的优势,创建一套本地数据用于 TableView 数据源。
//普通tableView数据源结构体
struct DataModel {
let descStr:String
let numStr:String
}
//普通tableView的data数据源
struct FirstTableViewModel {
var arr = Array<DataModel>()
init() {
arr.append(DataModel(descStr: "first", numStr: "number 1"))
arr.append(DataModel(descStr: "second", numStr: "number 2"))
arr.append(DataModel(descStr: "third", numStr: "number 3"))
arr.append(DataModel(descStr: "fourth", numStr: "number 4"))
arr.append((DataModel(descStr: "fifth", numStr: "number 5")))
}
}
在创建完成一个简单的数据源之后,再自定义一个 TableViewCell ,只是在里面添加两个 Lable 展示,具体实现直接看代码:
class normalTableViewCell: UITableViewCell {
var firstLable:UILabel?
var secondLable:UILabel?
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.firstLable = UILabel()
self.contentView.addSubview(self.firstLable!)
self.secondLable = UILabel()
self.contentView.addSubview(self.secondLable!)
}
override func layoutSubviews() {
super.layoutSubviews()
self.firstLable?.snp.makeConstraints({ (make) in
make.left.equalTo(self.contentView.snp.left).offset(10)
make.centerY.equalTo(self.contentView.snp.centerY)
make.width.equalTo(100)
make.height.equalTo(self.contentView.snp.height)
})
self.secondLable?.snp.makeConstraints({ (make) in
make.left.equalTo((self.firstLable?.snp.right)!).offset(10)
make.centerY.equalTo((self.firstLable?.snp.centerY)!)
make.width.equalTo((self.firstLable?.snp.width)!)
make.height.equalTo((self.firstLable?.snp.height)!)
})
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) not implemented yet")
}
func getValue(firstStr:String, secondStr:String) -> Void {
self.firstLable?.text = firstStr
self.secondLable?.text = secondStr
}
}
有了数据源,有了自定义的 cell,接下来就是重头戏
- 创建并初始化一个 tableView,既然是要用RxSwift,就不再需要声明遵循 TableView 的 DataSource 与 delegate 协议,RxSwift已经帮我做好了工作。
func createTableView() -> Void {
firstTableView = UITableView(frame: self.view.bounds, style: .plain)
view.addSubview(firstTableView)
//tableView行操作必须打开,才可移动cell
firstTableView.isEditing = true
firstTableView.backgroundColor = UIColor.orange
firstTableView.register(normalTableViewCell.self, forCellReuseIdentifier: resuerId)
}
- 将 TableView 与数据源进行绑定。这里使用 just 方法来创建一个 Observable 信号,并将与创建的 TableView 使用
bind
方法绑定。返回的参数中,分别包含tableView
、indexPath
与indexPath对应的数据模型
func bindViewModel() -> Void {
let items = Observable.just(FirstTableViewModel().arr)
items.bind(to: self.firstTableView.rx.items){(tb,row,model) -> UITableViewCell in
//其中对cell进行数据模型赋值,以此实现了数据模型model与视图View的分离
let cell = tb.dequeueReusableCell(withIdentifier: self.resuerId) as? normalTableViewCell
cell?.firstLable?.text = model.descStr
cell?.secondLable?.text = model.numStr
return cell!
}.disposed(by: disposeBag)
- TableView中的响应事件。RxSwift框架同样帮我完成了对 TableView 一系列事件的响应封装,比如:点击事件、删除cell事件、移动cell事件等。
func RxTableViewEvent() -> Void {
//cell选中点击事件
firstTableView.rx.modelSelected(DataModel.self).subscribe(onNext: { (model) in
print("modelSelected触发了cell点击,(model)")
})
.disposed(by: disposeBag)
//同样为cell选中点击事件订阅响应,但itemSelected订阅代码总是不提示,无奈
firstTableView.rx.itemSelected.subscribe(onNext: { indexPath in
print("itemSelected触发了cell点击,(indexPath)")
})
.disposed(by: disposeBag)
//订阅cell删除事件
firstTableView.rx.itemDeleted.subscribe(onNext: { (indexPath) in
print("删除了第(indexPath.row)个cell")
})
.disposed(by: disposeBag)
//订阅cell移动事件,tableView的isEditing属性必须设置为true才能生效
firstTableView.rx.itemMoved.subscribe(onNext: { (sourceIndexPath,desIndexPath) in
print("从(sourceIndexPath)移动到(desIndexPath)")
})
.disposed(by: disposeBag)
}
分组 tableView 应用
涉及到分组 tableView,首先需要引入 RxDataSource 框架,这里要注意:使用 RxDataSources 的唯一限制是,section 中使用的每个类型都必须符合 IdentifiableType 和Equatable协议。IdentifiableType协议是声明一个唯一的标识符(在同一具体类型的对象中是唯一的),以便RxDataSources唯一标识对象 惯例先准备好数据源
//组tableView数据结构体
struct SectionDataModel {
let firstName:String
let secondName:String
var image:UIImage?
init(firstName:String, secondName:String) {
self.firstName = firstName
self.secondName = secondName
image = UIImage(named: secondName)
}
}
//IdentifiableType声明一个唯一的标识符(在同一具体类型的对象中是唯一的),以便RxDataSources唯一标识对象
//这里是将secondName属性值作为唯一标识对象
extension SectionDataModel:IdentifiableType{
typealias Identity = String
var identity:Identity {return secondName}
}
//分组tableView数据源
class sectionData{
let sectionArr = Observable.just([
SectionModel(model: "one", items: [
SectionDataModel(firstName: "plan A", secondName: "A description"),
SectionDataModel(firstName: "plan B", secondName: "B descriptiopn"),
]),
SectionModel(model: "two", items: [
SectionDataModel(firstName: "plan AA", secondName: "AA description"),
SectionDataModel(firstName: "plan BB", secondName: "BB description"),
SectionDataModel(firstName: "plan CC", secondName: "CC description"),
]),
SectionModel(model: "three", items: [
SectionDataModel(firstName: "plan AAA", secondName: "AAA description"),
SectionDataModel(firstName: "plan BBB", secondName: "BBB description"),
SectionDataModel(firstName: "plan CCC", secondName: "CCC description"),
SectionDataModel(firstName: "plan DDD", secondName: "DDD description"),
])
])
}
分组 tableView 中的 cell 还是继续使用之前准备好的自定义 cell。
func createTableView() -> Void {
self.view.backgroundColor = UIColor.lightGray
sectionTableView = UITableView(frame: self.view.bounds, style: .plain)
sectionTableView.register(normalTableViewCell.self, forCellReuseIdentifier: normalTableViewCell.description())
self.view.addSubview(sectionTableView)
}
接下来就是重点,需要将封装成了 Observable 的数据源与 tableView 实现绑定并加载出对应的内容。
func bindViewModel() -> Void {
let dataS = RxTableViewSectionedReloadDataSource<SectionModel<String,SectionDataModel>>(configureCell: { (dataSource, desTableView, indexPath, model) -> UITableViewCell in
let cell = self.sectionTableView.dequeueReusableCell(withIdentifier: normalTableViewCell.description(), for: indexPath) as? normalTableViewCell
cell?.firstLable?.text = model.firstName
cell?.secondLable?.text = model.secondName
return cell!
}, titleForHeaderInSection: { (dataSource, index) -> String? in
return dataSource.sectionModels[index].model
})
sectionDatas.sectionArr.asDriver(onErrorJustReturn: [])
.drive(sectionTableView.rx.items(dataSource: dataS))
.disposed(by: disoposeBag)
}
使用 RxDataSource 中的 RxTableViewSectionedReloadDataSource<S: SectionModelType>
方法,而在上述代码中传入的参数,意思是传入泛型为 SectionModel 数组, SectionModel 中又包含子集。在上面准备好的数据中,第一个为 String 类型的header头部内容 model
,第二个为 SectionDataModel 类型的 items
。
继续输入 configureCell
方法,用于配置具体的 cell ,会出现两个方法提示
configCell代码提示
这两个方法的区别,从方法名来看,第一个只是需要配置 cell 其中的具体内容,第二个方法需要配置的东西非常多。但刚才的代码中,我只设置了每个 section 的头部内容。究其原因,查看了下方法实现
configureCell内部实现
所有属性都是用@escaping标明是逃逸闭包,换句话就是这个闭包在函数执行完成之后才被调用。除了 configureCell
之外,其它的所有方法都默认使用 nil 或空来初始化,也就是说, configureCell
是必须要实现的,而其它方法作为可选项来手动配置,若可选方法手动配置之后,会覆写其默认使用 nil 来初始化。
最后,sectionDatas 为var sectionDatas = sectionData()
的初始化之后变量,将包装成 Observable 的sectionArr
drive 发送给 sectionTableView 的 items 配置 DataSource。asDrive()
中配置的 onErrorJustReturn: []
,意义为当数据为 error 类型消息时,会返回给一个空数据,尤其是在请求数据异常时。
回看一下关于 RxSwift 框架对于 tableView 的封装,只需要几十行代码就可以完全配置出 tableView。回头会研究一下对多选 tableView 以及 cell 中输入内容等可编辑处理的情况。
上述代码已上传至GitHub,demo链接
- ASP.NET Core中的依赖注入(5):ServicePrvider实现揭秘【补充漏掉的细节】
- 了解ASP.NET MVC几种ActionResult的本质:FileResult
- ASP.NET Core管道深度剖析(1):采用管道处理HTTP请求
- 如何用Python和深度神经网络识别图像?
- 余军:分布式数据库在金融行业的创新实践
- 微信小游戏采用了我们都忽略的产品推广新切入点
- ASP.NET MVC下的四种验证编程方式[续篇]
- 如何把业务问题变成机器学习的问题?
- 这算是ASP.NET MVC的一个大BUG吗?
- 【Scikit-Learn 中文文档】分解成分中的信号(矩阵分解问题) - 无监督学习 - 用户指南 | ApacheCN
- 区块链技术在非能源领域的应用场景
- Python读书笔记8
- How to debug .NET Core RC2 app with Visual Studio Code on Windows?
- 难道.NET Core到R2连中文编码都不支持吗?
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 服务端的 WebAssembly 与 Rust 入门篇
- pImpl
- Flask+requests发起页面请求示例
- 【技术创作101训练营】Git 如何成功配置SSH key连接多个代码平台?
- 深入浅出iOS内存管理-技术创作101训练营
- 聊聊原型 Prototype | 技术创作101训练营
- Excelize 2.3.1 发布,Go 语言 Excel 文档基础库,支持加密表格文档
- PUMA560机器人工具箱运动控制A:路径规划-运动学
- Android 3分钟带你入门开发测试
- Spring Boot 知识清单(一)SpringApplication
- Linux下的IO监控与分析
- pytorch+Unet图像分割:将图片中的盐体找出来
- html 用浏览器打开中文乱码解决方法
- SQLServer 数据库字符集、版本号sql语句查询语法
- chrome 浏览器自保留端口、安全端口有哪些?chrome不能访问某个端口的环境网址,但是其它的浏览器可以访问原因及解决办法。