RxSwift介绍(五)——TableView的应用

时间:2022-06-19
本文章向大家介绍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,接下来就是重头戏

  1. 创建并初始化一个 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)
        }
  1. 将 TableView 与数据源进行绑定。这里使用 just 方法来创建一个 Observable 信号,并将与创建的 TableView 使用 bind方法绑定。返回的参数中,分别包含tableViewindexPathindexPath对应的数据模型
        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)
  1. 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链接