这次打算单独将 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 ,会出现两个方法提示
这两个方法的区别,从方法名来看,第一个只是需要配置 cell 其中的具体内容,第二个方法需要配置的东西非常多。但刚才的代码中,我只设置了每个 section 的头部内容。究其原因,查看了下方法实现
所有属性都是用@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链接
该文章首次发表在 简书:我只不过是出来写写代码 博客,并自动同步至 腾讯云:我只不过是出来写写iOS 博客