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链接


该文章首次发表在 简书:我只不过是出来写写代码 博客,并自动同步至 腾讯云:我只不过是出来写写iOS 博客

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,591评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,448评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,823评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,204评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,228评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,190评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,078评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,923评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,334评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,550评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,727评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,428评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,022评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,672评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,826评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,734评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,619评论 2 354

推荐阅读更多精彩内容