简体   繁体   English

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

[英]How to handle two table view association?

I am a new Swifter, Here is the code of my new company. 我是新来的Swifter,这是我新公司的代码。

Use RxSwift,use RxDataSource, how to handle two table view association? 使用RxSwift,使用RxDataSource,如何处理两个表视图的关联?

The left tableView's cell clicked , the right tableView's data changed along. 左侧tableView的单元格单击,右侧tableView的数据随之更改。

Organize the right table view's data via the variables of middle state. 通过中间状态变量组织右表视图的数据。

Bad code's smell。 错误代码的味道。

Here is the image 这是图片

一

Here is the code: 这是代码:

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 , used to compare the old and new selected index of the left table View ,let the right table view start , with currentIndex , if different private var lastIndex : NSInteger = 0 ,用于比较左表View的旧索引和新索引,将右表start以currentIndex (如果不同)

the self.currentSelectIndexPath was used, because when the left tableView's final cell got clicked, the UI logic is different. self.currentSelectIndexPath使用self.currentSelectIndexPath是因为单击左tableView的最后一个单元格时,UI逻辑不同。

self.currentListData is used to capture the current data of the right table view, when the left tableView clicked with different row. self.currentListData用于捕获右表视图的当前数据,当左表视图用不同的行单击时。

self.currentListData is also used in the UITableViewDelegate . 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
        }
    }
}

How to improve the code? 如何改进代码?

How to eliminate the variables of middle state. 如何消除中间状态变量。

The corresponding model is 对应的型号是

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

I get rid of two property , which is used for communication with values. 我摆脱了两个property,它用于与值进行通信。

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)
  // ······

I use the tip of RxDataSources's issue 我使用RxDataSources问题的提示

lastIndex works with currentIndex , to make sure the right table view is always at the top, when the user switches the left tableView's indexPath. lastIndexcurrentIndex ,以确保当用户切换左tableView的indexPath时,右表视图始终位于顶部。

The moment matters, selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none) . 当下很重要, selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)

This should be called when the table View ends Updated. 表视图结束更新时应调用此方法。

If this could be done in Rx way, the two variable is useless. 如果可以用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(())
    }
}

Then handle the tableView. 然后处理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)
        }
    }



}

With the same tips, lastIndex is also used to give the right table View the initial data. 使用相同的技巧, lastIndex也用于给正确的表查看初始数据。

The moment also matters. 此刻也很重要。

This could be done after the left table view ends updated, too 这也可以在左表视图结束更新后完成

It is easy to get rid of private var currentListData :[SubItems]? 很容易摆脱private var currentListData :[SubItems]? , with some encapsulation code, based on the code above. ,并根据上面的代码提供了一些封装代码。

Because you have currentSelectIndexPath , then it is easy to get currentListData by calculating. 因为您具有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
        }
    }
}

Notice the logic of last row is quite different from the others. 请注意,最后一行的逻辑与其他逻辑完全不同。

So it is better to be another section , or a footer in this case. 因此,最好是另辟be节,或者在这种情况下是页脚。

With tableView.rx.model to get models, you can get rid of currentSelectIndexPath with the following code: 使用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  ...
    }

}

And the model should be changed a little 模型应该改变一点

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: [])

 // ...

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用推键处理两个集合视图 - how to handle two collection view with push segues 如何添加额外的表格视图单元格来处理用户输入? - How to add extra table view cells to handle user input? Swift:如何处理表视图与方法更新表视图之间的同步 - Swift: How to handle synchronisation between table view and method updating table view 快速处理表格视图控制器的详细信息页面 - handle detail page for table view controller in swift 如何快速订购两个动态表格视图单元格? - How can I order two dynamic table view cells in swift? 如何在表视图 swift 中使用 header 获取两个部分的数据 - How to get data in two sections with header in table view swift 如何在 iOS 中使用键盘处理视图 - How to handle view with keyboard in iOS 如何处理在第二个表格视图的行中快速点击第一表格视图单元格下面的水龙头? - How to handle tap on row of 2nd tableView which is under the 1st table view cell in swift? 一个视图控制器中的两个表视图,快速 - two table view in one view controller, swift 如何使用两个自定义 UITableViewCell 在一个视图控制器中创建两个表视图? - How do I create two table views in one view controller with two custom UITableViewCells?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM