Как обрабатывать две таблицы представления ассоциации? - PullRequest
1 голос
/ 28 апреля 2019

Я новый Swifter, вот код моей новой компании.

Использовать RxSwift , Использовать RxDataSource, как обработать связь двух табличных представлений?

Нажата ячейка левой табличной панели,данные правого 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, используемый для сравнения старого и нового выбранного индекса левой таблицы View,позвольте правому табличному представлению начать с currentIndex, если другое

использовалось self.currentSelectIndexPath, потому что, когда щелкали последнюю ячейку левого tableView, логика пользовательского интерфейса отличается.

self.currentListData используется для захвата текущих данных правого табличного представления, когда левый табличный просмотр щелкается с другой строкой.

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 голосов
/ 05 мая 2019

Легко избавиться от 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
        }
    }
}

Обратите внимание, что логика последнего ряда довольно отличается от других.

Так что в этом случае лучше быть другим разделом или нижним колонтитулом.

С помощью 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: [])

 // ...
0 голосов
/ 30 апреля 2019

Я избавляюсь от двух свойств, которые используются для связи со значениями.

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

lastIndex работаетс currentIndex, чтобы убедиться, что правое представление таблицы всегда вверху, когда пользователь переключает indexPath левого tableView.

Момент имеет значение, selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none).

Это должно бытьвызывается, когда заканчивается просмотр таблицы. Обновлено.

Если это можно сделать способом 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 также используется, чтобы дать правильную таблицу. Просмотр исходных данных.

Момент также имеет значение.

Это можно сделать и после обновления левого представления таблицы, тоже обновленного

...