RxSwift介紹(五)——TableView的應(yīng)用

這次打算單獨(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,接下來就是重頭戲

  1. 創(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)
        }
  1. 將 TableView 與數(shù)據(jù)源進(jìn)行綁定。這里使用 just 方法來創(chuàng)建一個 Observable 信號,并將與創(chuàng)建的 TableView 使用 bind方法綁定。返回的參數(shù)中,分別包含tableViewindexPathindexPath對應(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)
  1. 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)兩個方法提示

configCell代碼提示

這兩個方法的區(qū)別,從方法名來看,第一個只是需要配置 cell 其中的具體內(nèi)容,第二個方法需要配置的東西非常多。但剛才的代碼中,我只設(shè)置了每個 section 的頭部內(nèi)容。究其原因,查看了下方法實現(xiàn)
configureCell內(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 博客

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • 1、通過CocoaPods安裝項目名稱項目信息 AFNetworking網(wǎng)絡(luò)請求組件 FMDB本地數(shù)據(jù)庫組件 SD...
    陽明AGI閱讀 16,003評論 3 119
  • 1.ios高性能編程 (1).內(nèi)層 最小的內(nèi)層平均值和峰值(2).耗電量 高效的算法和數(shù)據(jù)結(jié)構(gòu)(3).初始化時...
    歐辰_OSR閱讀 29,547評論 8 265
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對...
    cosWriter閱讀 11,135評論 1 32
  • 我和那個姑娘已經(jīng)認(rèn)識12年了。我們沒有似曾相識的初見,也沒有惺惺相惜的守護(hù)。我們也不幸的落入電視劇般的俗套中,做了...
    Echo日記閱讀 358評論 0 0
  • 我還是原來的我,只是物是人非罷了。我現(xiàn)在學(xué)什么,都是為了用,都是為了賺錢,都是為了有朝一日出人頭地,光耀門楣! 根...
    王寶玉啊閱讀 770評論 0 1