RxDataSource 使用套路与解释

栏目: IOS · 发布时间: 7年前

内容简介:场景举例,就是两表关联。 RxDataSource,怎样列表刷新出来,就自动选择第一个。然后子列表根据上一个列表的选择,确认要刷新的数据。问题是 RxDataSource 专注于列表视图的数据处理,自动选择第一个 row 是 tableViewDelegate 干的事情。左边一个列表 tableView, 右边一个列表 tableView , 两个列表之间的数据存在关联,左边列表是一级选项,右边列表是对应左边的二级选项。

场景举例,就是两表关联。 RxDataSource,怎样列表刷新出来,就自动选择第一个。然后子列表根据上一个列表的选择,确认要刷新的数据。

问题是 RxDataSource 专注于列表视图的数据处理,自动选择第一个 row 是 tableViewDelegate 干的事情。

RxDataSource 使用套路与解释

左边一个列表 tableView, 右边一个列表 tableView , 两个列表之间的数据存在关联,左边列表是一级选项,右边列表是对应左边的二级选项。

一般可以这么做:

private var lastIndex : NSInteger = 0

// ...

// 看这一段代码,就够了
let leftDataSource = RxTableViewSectionedReloadDataSource<CategoryLeftSection>( configureCell: { [weak self] ds, tv, ip, item in
            guard  let strongSelf = self else { return UITableViewCell()}
            let cell : CategoryLeftCell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
            cell.model = item
            // 看这一句代码,就够了
            if ip.row == strongSelf.lastIndex {
                   // ...
                    tv.selectRow(at: ip, animated: false, scrollPosition: .top)
                    tv.delegate?.tableView!(tv, didSelectRowAt: ip)
            }
            return cell
        })
        
        vmOutput!.sections.asDriver().drive(self.leftMenuTableView.rx.items(dataSource: leftDataSource)).disposed(by: rx.disposeBag)

// 选择左边列表,给右边提供的数据
let rightPieceListData = self.leftMenuTableView.rx.itemSelected.distinctUntilChanged().flatMapLatest {
        [weak self](indexPath) ->  Observable<[SubItems]> in
            guard let strongSelf = self else { return Observable.just([]) }
           
            strongSelf.currentIndex = indexPath.row
            if indexPath.row == strongSelf.viewModel.vmDatas.value.count - 1 {
                // ...
                strongSelf.leftMenuTableView.selectRow(at: strongSelf.currentSelectIndexPath, animated: false, scrollPosition: .top)
                strongSelf.leftMenuTableView.delegate?.tableView!(strongSelf.leftMenuTableView, didSelectRowAt: strongSelf.currentSelectIndexPath!)
                return Observable.just((strongSelf.currentListData)!)
            }
            if let subItems = strongSelf.viewModel.vmDatas.value[indexPath.row].subnav {
                // ...
                var reult:[SubItems] = subItems
                // ...
                strongSelf.currentListData = reult
                strongSelf.currentSelectIndexPath = indexPath
                return Observable.just(reult)
            }
            return Observable.just([])
        }.share(replay: 1)
        
        // 右边列表的数据源,具体 cell 的配置方法
        let rightListDataSource =  RxTableViewSectionedReloadDataSource<CategoryRightSection>( configureCell: { [weak self]ds, tv, ip, item in
            guard let strongSelf = self else { return UITableViewCell() }
            if strongSelf.lastIndex != strongSelf.currentIndex {
                tv.scrollToRow(at: ip, at: .top, animated: false)
                strongSelf.lastIndex = strongSelf.currentIndex
            }
            if ip.row == 0 {
                let cell :CategoryListBannerCell = CategoryListBannerCell()
                cell.model = item
                return cell
            } else {
                let cell : CategoryListSectionCell = tv.dequeueReusableCell(withIdentifier: "Cell2", for: ip) as! CategoryListSectionCell
                cell.model = item
                return cell
            }
        })
        // 设置右边列表的代理
        rightListTableView.rx.setDelegate(self).disposed(by: rx.disposeBag)
        // 右边列表需要取得的数据,传递给右边列表的配置方法
        rightPieceListData.map{ [CategoryRightSection(items:$0)] }.bind(to: self.rightListTableView.rx.items(dataSource: rightListDataSource))
            .disposed(by: rx.disposeBag)
复制代码

上面这个实现很 Low , 选择的时机是这个 if ip.row == strongSelf.lastIndex { , 当更新数据到指定 cell 时候,操作。

程序可以跑,但是不优雅。

如果把上面的逻辑翻译成面向对象,就是:

open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
           let cell : CategoryLeftCell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
            cell.model = item
            // 看这一句代码,就够了
            if ip.row == strongSelf.lastIndex {
                   // ...
                    tv.selectRow(at: ip, animated: false, scrollPosition: .top)
                    tv.delegate?.tableView!(tv, didSelectRowAt: ip)
                    // ...
                
            }
            return cell
    }
复制代码

这样展开,清晰了一些。我们是不会这么用的,如果不用 Rx, 直接 OOP. 我们是这么用 刷新完了之后,选中一下

tableView.reloadData()
tv.selectRow(at: ip, animated: false, scrollPosition: .top)

复制代码

// 其他操作

open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            let cell : CategoryLeftCell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
            cell.model = item
            return cell
    }
复制代码

看一看源码

leftDataSource 通过泛型指明每个 tableView section 的数据结构,他唯一的参数是一个闭包, configureCell .

let rightListDataSource =  RxTableViewSectionedReloadDataSource<CategoryLeftSection>( configureCell: { [weak self] ds, tv, ip, item in
// ...
}

复制代码

configureCell 实际上就是系统提供的代理方法 open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {

RxDataSource 的源代码挺清晰的,首先采用前提条件 precondition , row 确保不会越界。然后调用 configureCell 匿名函数。这样的设计,借用了 Swift 中函数是一级公民,函数可以像值一样传递。

open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        precondition(indexPath.item < _sectionModels[indexPath.section].items.count)
        
        return configureCell(self, tableView, indexPath, self[indexPath])
    }
复制代码

更好的调用时机:

继承 TableViewSectionedDataSource , 创建自己想要的子类,任意根据需求改方法。

做一个继承

final class MyDataSource<S: SectionModelType>: RxTableViewSectionedReloadDataSource<S> {
    private let relay = PublishRelay<Void>()
    var rxRealoded: Signal<Void> {
        return relay.asSignal()
    }
    
    override func tableView(_ tableView: UITableView, observedEvent: Event<[S]>) {
        super.tableView(tableView, observedEvent: observedEvent)
        // Do diff
        // Notify update
        relay.accept(())
    }
    
}

复制代码

因为这个场景下, super.tableView(tableView, observedEvent: observedEvent) , 调用之后,需要接受一下事件,以后把它发送出去。所以要用一个 PublishSubject . PublishRelay 是对 PublishSubject 的封装,区别是他的功能没 PublishSubject 那么强, PublishRelay 少两个状态,完成 completed 和出错 error, 适合更加专门的场景。

Signal 信号嘛,共享事件流 SharedSequence。他是对 Observable 的封装。具有在主线程调用等特性,更适用于搞 UI .

简单优化下,代码语义更加明确

// left menu 数据源
        let leftDataSource = MyDataSource<CategoryLeftSection>( configureCell: { ds, tv, ip, item in
            let cell : CategoryLeftCell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
            cell.model = item
            return cell
        })
        // 刷新完了,做一下选择
        leftDataSource.rxRealoded.emit(onNext: { [weak self] in
            guard let self = self else { return }
            let indexPath = IndexPath(row: 0, section: 0)
            self.leftMenuTableView.selectRow(at: indexPath, animated: false, scrollPosition: UITableView.ScrollPosition.none)
            self.leftMenuTableView.delegate?.tableView?(self.leftMenuTableView, didSelectRowAt: indexPath)
        }).disposed(by: rx.disposeBag)

// ...
// 其余不变

复制代码

所谓代码的语义

FRP 就是把要干嘛,直接写清楚,不会像 OOP 那样,过度的见缝插针,调用到了,就改一下状态。

声明式编程是只在一处修改。就算把那一处修改,声明式编程也只在一处调用。OOP 就找的比较辛苦。

git repo, 我放在 coding.net 上面了,pod 都装好了,下载了直接跑


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Programming Ruby

Programming Ruby

Dave Thomas、Chad Fowler、Andy Hunt / Pragmatic Bookshelf / 2004-10-8 / USD 44.95

Ruby is an increasingly popular, fully object-oriented dynamic programming language, hailed by many practitioners as the finest and most useful language available today. When Ruby first burst onto the......一起来看看 《Programming Ruby》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

URL 编码/解码
URL 编码/解码

URL 编码/解码