如何处理两个表视图的关联?

时间:2019-04-28 10:18:13

标签: swift rx-swift rxdatasources

我是一名新的Swifter,这是我新公司的代码。

使用RxSwift,使用RxDataSource,如何处理两个表视图的关联?

单击了左tableView的单元格,同时更改了右tableView的数据。

通过中间状态变量来组织右表视图的数据。

错误代码的味道。

这是图片

one

这是代码:

private let viewModel = CategoryViewModel()
private var currentListData :[SubItems]?
private var lastIndex : NSInteger = 0
private var currentSelectIndexPath : IndexPath?
private var currentIndex : NSInteger = 0

private func boundTableViewData() {

    var loadCount = 0
    // data source of left table view
    let dataSource = RxTableViewSectionedReloadDataSource<CategoryLeftSection>( configureCell: { ds, tv, ip, item in
    let cell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
    cell.model = item
     if ip.row == 0, !cell.isSelected {
          // in order to give the right table view a start show
             tv.selectRow(at: ip, animated: false, scrollPosition: .top)
             tv.delegate?.tableView!(tv, didSelectRowAt: ip)

        }
       return cell
    })

    vmOutput!.sections.asDriver().drive(leftMenuTableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)

   // organize the right table view's data via the variables of middle state.

  // bad code's smell
    let listData = leftMenuTableView.rx.itemSelected.distinctUntilChanged().flatMapLatest {
        [weak self](indexPath) ->  Observable<[SubItems]> in
            guard let self = self else { return Observable.just([]) }
            // ...
            self.currentIndex = indexPath.row
            if indexPath.row == self.viewModel.vmDatas.value.count - 1 {
                // ...
           // the self.currentSelectIndexPath was used, because when the left tableView's final cell got clicked, the  UI logic is different.
                self.leftMenuTableView.selectRow(at: self.currentSelectIndexPath, animated: false, scrollPosition: .top)
                return Observable.just((self.currentListData)!)
            }
            if let subItems = self.viewModel.vmDatas.value[indexPath.row].subnav {
                var fisrtSubItem = SubItems()
                fisrtSubItem.url = self.viewModel.vmDatas.value[indexPath.row].url
                fisrtSubItem.name = self.viewModel.vmDatas.value[indexPath.row].banner
                var reult:[SubItems] = subItems
                reult.insert(fisrtSubItem, at: 0)
                self.currentListData = reult
              //  self.currentListData is used to capture the current data of the right table view.
                self.currentSelectIndexPath = indexPath
                return Observable.just(reult)
            }
            return Observable.just([])
    }.share(replay: 1)

    // data source of right table view    
     let listDataSource =  RxTableViewSectionedReloadDataSource<CategoryRightSection>( configureCell: { [weak self]ds, tv, ip, item in
            guard let self = self else { return UITableViewCell() }
            if self.lastIndex != self.currentIndex {
           // to compare the old and new selected index of the left table View ,give a new start to the right table view if changed
                tv.scrollToRow(at: ip, at: .top, animated: false)
                self.lastIndex = self.currentIndex
            }
            if ip.row == 0 {
                let cell = CategoryListBannerCell()
                cell.model = item
                return cell
            } else {
                let cell = tv.dequeueReusableCell(withIdentifier: "Cell2", for: ip) as! CategoryListSectionCell
                cell.model = item
                return cell
            }
     })


     listData.map{ [CategoryRightSection(items:$0)] }.bind(to: rightListTableView.rx.items(dataSource: listDataSource))
            .disposed(by: rx.disposeBag)   
 }     

private var lastIndex : NSInteger = 0,用于比较左表视图的旧索引和新选择的索引,如果不同,则将右表视图start设为currentIndex

使用self.currentSelectIndexPath是因为单击左tableView的最后一个单元格时,UI逻辑不同。

self.currentListData用于在单击左tableView的不同行时捕获右表视图的当前数据。

self.currentListData也用在UITableViewDelegate中。

// MARK:- UITableViewDelegate
extension CategoryViewController : UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        switch indexPath.row {
        case 0 :
            return (mScreenW - 120)/240 * 100;
        default :
            let subItems:SubItems = self.currentListData![indexPath.row]
            if subItems.children.count > 0{
                let lines: NSInteger = (subItems.children.count - 1)/3 + 1
                let buttonHeight = (mScreenW - 136 - 108)/3
                let allButtonHeight = buttonHeight/44 * 63 * CGFloat(lines)
                let other =  (lines - 1)*42 + 56
                let height = allButtonHeight  + CGFloat(other) + 33
                return height
            }
            return 250
        }
    }
}

如何改进代码?

如何消除中间状态变量。

对应的模型是

class CategoryViewModel: NSObject {

    let vmDatas = Variable<[ParentItem]>([])

    func transform() -> MCBoutiqueOutput {

        let temp_sections = vmDatas.asObservable().map({ (sections) -> [CategoryLeftSection] in
            let count = sections.count
            if count > 0{
                let items = sections[0..<(count-1)]
                return [CategoryLeftSection(items: Array(items))]
            }
            return []
        }).asDriver(onErrorJustReturn: [])

        let output = MCBoutiqueOutput(sections: temp_sections)
        Observable.combineLatest(output.requestCommand, Provider.rx.cacheRequest(.baseUIData)).subscribe({  [weak self]  ( result: Event<(Bool, Response)>) in
            guard let self = self else { return }
            switch result{
            case .next(let response):
                let resultReal = response.1
                // ...
                if resultReal.statusCode == 200 || resultReal.statusCode == 230 {

                    if resultReal.fetchJSONString(keys:["code"]) == "0" {
                        mUserDefaults.set(false, forKey: "categoryVCShowTry")
                        self.vmDatas.value = ParentItem.mapModels(from:
                            resultReal.fetchJSONString(keys:["data","data"]))
                    } 
                }
            default:
                break
            }
        }).disposed(by: rx.disposeBag)
        return output
    }
}

2 个答案:

答案 0 :(得分:0)

我摆脱了两个用于与值​​进行通信的属性。

private let viewModel = CategoryViewModel()
    private var currentListData :[SubItems]?

    private var currentSelectIndexPath : IndexPath?


    func getResult(_ row: Int) -> [SubItems]{
        if viewModel.vmDatas.value.isEmpty == false, let subItems = viewModel.vmDatas.value[row].subnav {
            var fisrtSubItem = SubItems()
            fisrtSubItem.url = self.viewModel.vmDatas.value[row].url
            fisrtSubItem.name = self.viewModel.vmDatas.value[row].banner
            var result:[SubItems] = subItems
            result.insert(fisrtSubItem, at: 0)
            return result
        }
        return []
    }




    private func boundTableViewData() {

        //// left menu 数据源
        let dataSource = MyDataSource<CategoryLeftSection>( configureCell: { ds, tv, ip, item in
            let cell = tv.dequeueReusableCell(withIdentifier: "Cell1", for: ip) as! CategoryLeftCell
            cell.model = item
            return cell
        })
        dataSource.rxRealoded.emit(onNext: { [weak self] in
            guard let self = self else { return }
            self.leftMenuTableView.selectIndexPath()
            self.leftMenuTableView.clickIndexPath()
        }).disposed(by: rx.disposeBag)
        vmOutput!.sections.asDriver().drive(leftMenuTableView.rx.items(dataSource: dataSource)).disposed(by: rx.disposeBag)

        /// list 数据依赖 左侧点击
        let listData = leftMenuTableView.rx.itemSelected.distinctUntilChanged().flatMapLatest {
        [weak self](indexPath) ->  Observable<[SubItems]> in
            guard let self = self else { return Observable.just([]) }
            // ...
            if indexPath.row == self.viewModel.vmDatas.value.count - 1 {
                // ...
                self.leftMenuTableView.selectRow(at: self.currentSelectIndexPath, animated: false, scrollPosition: .top)
                return Observable.just((self.currentListData)!)
            }
            let result:[SubItems] = self.getResult(indexPath.row)
            self.currentListData = result
            self.currentSelectIndexPath = indexPath
            return Observable.just(result)

        }.share(replay: 1)


        let listDataSource = MyDataSource<CategoryRightSection>(configureCell: { ds, tv, ip, item in
            if ip.row == 0 {
                let cell = CategoryListBannerCell()
                cell.model = item
                return cell
            } else {
                let cell = tv.dequeueReusableCell(withIdentifier: "Cell2", for: ip) as! CategoryListSectionCell
                cell.model = item
                return cell
            }
        })

        listDataSource.rxRealoded.emit(onNext: { [weak self] in
            guard let self = self else { return }
            self.rightListTableView.scrollToTop(animated: false)
        }).disposed(by: rx.disposeBag)

        listData.map{ [CategoryRightSection(items:$0)] }.bind(to: rightListTableView.rx.items(dataSource: listDataSource))
            .disposed(by: rx.disposeBag)
  // ······

我使用RxDataSources's issue

的小费

lastIndexcurrentIndex一起使用,以确保当用户切换左tableView的indexPath时,右表视图始终位于顶部。

此刻至关重要,selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)

表View结束更新时应调用它。

如果可以通过Rx方式完成操作,则这两个变量将无用。

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(())
    }
}

然后处理tableView。

extension UITableView {
    func hasRowAtIndexPath(indexPath: IndexPath) -> Bool {
        return indexPath.section < numberOfSections && indexPath.row < numberOfRows(inSection: indexPath.section)
    }

    func scrollToTop(animated: Bool) {
        let indexPath = IndexPath(row: 0, section: 0)
        if hasRowAtIndexPath(indexPath: indexPath) {
            scrollToRow(at: indexPath, at: .top, animated: animated)
        }
    }


    func selectIndexPath(indexPath: IndexPath? = nil) {
        let indexPath = IndexPath(row: 0, section: 0)
        if hasRowAtIndexPath(indexPath: indexPath) {
            selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)
        }
    }

    func clickIndexPath(indexPath: IndexPath? = nil) {
        let indexPath = IndexPath(row: 0, section: 0)
        if hasRowAtIndexPath(indexPath: indexPath) {
            delegate?.tableView?(self, didSelectRowAt: indexPath)
        }
    }



}

使用相同的技巧,lastIndex也用于提供正确的表查看初始数据。

这一刻也很重要。

这也可以在左表视图结束更新后完成

答案 1 :(得分:0)

根据上面的代码,使用一些封装代码很容易摆脱private var currentListData :[SubItems]?

由于您有currentSelectIndexPath,因此通过计算很容易获得currentListData

private var currentSelectIndexPath = IndexPath(item: 0, section: 0)

private func boundTableViewData() {
    /// list 数据依赖 左侧点击
        let listData = leftMenuTableView.rx.itemSelected.flatMapLatest {
        [weak self](indexPath) ->  Observable<[SubItems]> in
            guard let self = self else { return Observable.just([]) }
            // ...
            if indexPath.row == self.viewModel.vmDatas.value.count - 1 {
                // ...
                self.leftMenuTableView.selectRow(at: self.currentSelectIndexPath, animated: false, scrollPosition: .top)
                return Observable.just(self.getResult(self.currentSelectIndexPath.row))
            }
            let result:[SubItems] = self.getResult(indexPath.row)
            self.currentSelectIndexPath = indexPath
            return Observable.just(result)

        }

   // ...
}


// MARK:- UITableViewDelegate
extension CategoryViewController : UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        guard tableView == self.rightListTableView else {
            return Metric.leftMenuHeight
        }
        switch indexPath.row {
        case 0 :
            return (mScreenW - 120)/240 * 100;
        default :
            let subItems:SubItems = getResult(currentSelectIndexPath.row)[indexPath.row]
            if subItems.children.count > 0{
                let lines: NSInteger = (subItems.children.count - 1)/3 + 1
                let buttonHeight = (mScreenW - 136 - 108)/3
                let allButtonHeight = buttonHeight/44 * 63 * CGFloat(lines)
                let other =  (lines - 1)*42 + 56
                let height = allButtonHeight  + CGFloat(other) + 33
                return height
            }
            return 250
        }
    }
}

请注意,最后一行的逻辑与其他逻辑完全不同。

因此,最好是另辟be节,或者在这种情况下是页脚。

使用tableView.rx.model获取模型,可以使用以下代码摆脱currentSelectIndexPath

private func boundTableViewData() {
        // ...
        let listData = leftMenuTableView.rx.itemSelected.distinctUntilChanged().flatMapLatest {
        [weak self](indexPath) ->  Observable<[SubItems]> in
            guard let self = self else { return Observable.just([]) }
            // ...
            let result:[SubItems] = self.getResult(indexPath.row)
            return Observable.just(result)
        }


// MARK:- UITableViewDelegate
extension CategoryViewController : UITableViewDelegate {
    func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        switch indexPath.row {
        case 0 :
            return (mScreenW - 120)/240 * 100
        default :
            if let subItems : SubItems = try? tableView.rx.model(at: indexPath), subItems.children.count > 0{
                let lines: NSInteger = (subItems.children.count - 1)/3 + 1
                let buttonHeight = (mScreenW - 136 - 108)/3
                let allButtonHeight = buttonHeight/44 * 63 * CGFloat(lines)
                let other =  (lines - 1)*42 + 56
                let height = allButtonHeight  + CGFloat(other) + 33
                return height
            }
            return 250
        }
    }

    func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        return nil
    }

    func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        return CGFloat.zero
    }

    func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        // ...
        let cell = CategoryLeftCell()
        return cell
    }


    func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        // return  ...
    }

}

该模型应该稍作更改

class CategoryViewModel: NSObject {

    let vmDatas = Variable<[ParentItem]>([])

    func transform() -> MCBoutiqueOutput {

        let temp_sections = vmDatas.asObservable().map({ (sections) -> [CategoryLeftSection] in
            let count = sections.count
            if count > 0{
                let items = sections[0..<(count-1)]
                return [CategoryLeftSection(items: Array(items))]
            }
            return []
        }).asDriver(onErrorJustReturn: [])

 // ...