這次打算單獨(dú)將 tableView 在 RxSwift 框架中的使用整理成一篇文章。tableView 在日常開發(fā)中是接觸到最多的UI控件之一,在 RxSwift 框架中也幫我們封裝好了關(guān)于 tableview 的使用方法。自從接觸了 RxSwift 的框架,關(guān)于 tableview 基本就不用再繁瑣地去實現(xiàn)系統(tǒng)提供的各種代理方法,幾行代碼搞定關(guān)于 tableview 的一切。順便吐槽一下編譯器RxSwift代碼提示總是無法及時顯示,甚至得手寫方法名和參數(shù),尤其是 tableview ...
TableView 基本應(yīng)用
首先,為了更好地展示 RxSwift 在 TableView 方面的優(yōu)勢,創(chuàng)建一套本地數(shù)據(jù)用于 TableView 數(shù)據(jù)源。
//普通tableView數(shù)據(jù)源結(jié)構(gòu)體
struct DataModel {
let descStr:String
let numStr:String
}
//普通tableView的data數(shù)據(jù)源
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")))
}
}
在創(chuàng)建完成一個簡單的數(shù)據(jù)源之后,再自定義一個 TableViewCell ,只是在里面添加兩個 Lable 展示,具體實現(xiàn)直接看代碼:
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
}
}
有了數(shù)據(jù)源,有了自定義的 cell,接下來就是重頭戲
- 創(chuàng)建并初始化一個 tableView,既然是要用RxSwift,就不再需要聲明遵循 TableView 的 DataSource 與 delegate 協(xié)議,RxSwift已經(jīng)幫我做好了工作。
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 與數(shù)據(jù)源進(jìn)行綁定。這里使用 just 方法來創(chuàng)建一個 Observable 信號,并將與創(chuàng)建的 TableView 使用
bind
方法綁定。返回的參數(shù)中,分別包含tableView
、indexPath
與indexPath對應(yīng)的數(shù)據(jù)模型
func bindViewModel() -> Void {
let items = Observable.just(FirstTableViewModel().arr)
items.bind(to: self.firstTableView.rx.items){(tb,row,model) -> UITableViewCell in
//其中對cell進(jìn)行數(shù)據(jù)模型賦值,以此實現(xiàn)了數(shù)據(jù)模型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中的響應(yīng)事件。RxSwift框架同樣幫我完成了對 TableView 一系列事件的響應(yīng)封裝,比如:點擊事件、刪除cell事件、移動cell事件等。
func RxTableViewEvent() -> Void {
//cell選中點擊事件
firstTableView.rx.modelSelected(DataModel.self).subscribe(onNext: { (model) in
print("modelSelected觸發(fā)了cell點擊,\(model)")
})
.disposed(by: disposeBag)
//同樣為cell選中點擊事件訂閱響應(yīng),但itemSelected訂閱代碼總是不提示,無奈
firstTableView.rx.itemSelected.subscribe(onNext: { indexPath in
print("itemSelected觸發(fā)了cell點擊,\(indexPath)")
})
.disposed(by: disposeBag)
//訂閱cell刪除事件
firstTableView.rx.itemDeleted.subscribe(onNext: { (indexPath) in
print("刪除了第\(indexPath.row)個cell")
})
.disposed(by: disposeBag)
//訂閱cell移動事件,tableView的isEditing屬性必須設(shè)置為true才能生效
firstTableView.rx.itemMoved.subscribe(onNext: { (sourceIndexPath,desIndexPath) in
print("從\(sourceIndexPath)移動到\(desIndexPath)")
})
.disposed(by: disposeBag)
}
分組 tableView 應(yīng)用
涉及到分組 tableView,首先需要引入 RxDataSource 框架,這里要注意:使用 RxDataSources 的唯一限制是,section 中使用的每個類型都必須符合 IdentifiableType 和Equatable協(xié)議。IdentifiableType協(xié)議是聲明一個唯一的標(biāo)識符(在同一具體類型的對象中是唯一的),以便RxDataSources唯一標(biāo)識對象
慣例先準(zhǔn)備好數(shù)據(jù)源
//組tableView數(shù)據(jù)結(jié)構(gòu)體
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聲明一個唯一的標(biāo)識符(在同一具體類型的對象中是唯一的),以便RxDataSources唯一標(biāo)識對象
//這里是將secondName屬性值作為唯一標(biāo)識對象
extension SectionDataModel:IdentifiableType{
typealias Identity = String
var identity:Identity {return secondName}
}
//分組tableView數(shù)據(jù)源
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 還是繼續(xù)使用之前準(zhǔn)備好的自定義 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 的數(shù)據(jù)源與 tableView 實現(xiàn)綁定并加載出對應(yīng)的內(nèi)容。
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>
方法,而在上述代碼中傳入的參數(shù),意思是傳入泛型為 SectionModel 數(shù)組, SectionModel 中又包含子集。在上面準(zhǔn)備好的數(shù)據(jù)中,第一個為 String 類型的header頭部內(nèi)容 model
,第二個為 SectionDataModel 類型的 items
。
繼續(xù)輸入 configureCell
方法,用于配置具體的 cell ,會出現(xiàn)兩個方法提示
這兩個方法的區(qū)別,從方法名來看,第一個只是需要配置 cell 其中的具體內(nèi)容,第二個方法需要配置的東西非常多。但剛才的代碼中,我只設(shè)置了每個 section 的頭部內(nèi)容。究其原因,查看了下方法實現(xiàn)
所有屬性都是用@escaping標(biāo)明是逃逸閉包,換句話就是這個閉包在函數(shù)執(zhí)行完成之后才被調(diào)用。除了
configureCell
之外,其它的所有方法都默認(rèn)使用 nil 或空來初始化,也就是說, configureCell
是必須要實現(xiàn)的,而其它方法作為可選項來手動配置,若可選方法手動配置之后,會覆寫其默認(rèn)使用 nil 來初始化。最后,sectionDatas 為
var sectionDatas = sectionData()
的初始化之后變量,將包裝成 Observable 的sectionArr
drive 發(fā)送給 sectionTableView 的 items 配置 DataSource。asDrive()
中配置的 onErrorJustReturn: []
,意義為當(dāng)數(shù)據(jù)為 error 類型消息時,會返回給一個空數(shù)據(jù),尤其是在請求數(shù)據(jù)異常時。
回看一下關(guān)于 RxSwift 框架對于 tableView 的封裝,只需要幾十行代碼就可以完全配置出 tableView。回頭會研究一下對多選 tableView 以及 cell 中輸入內(nèi)容等可編輯處理的情況。
上述代碼已上傳至GitHub,demo鏈接
該文章首次發(fā)表在 簡書:我只不過是出來寫寫代碼 博客,并自動同步至 騰訊云:我只不過是出來寫寫iOS 博客