如何处理两个 table 视图关联?
How to handle two table view association?
我是新来的Swifter,这是我新公司的代码。
使用RxSwift,使用RxDataSource,如何处理两个table视图关联?
点击了左侧 tableView 的单元格,右侧 tableView 的数据随之改变。
通过中间状态的变量组织右 table 视图的数据。
坏代码的味道。
这是图片
代码如下:
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:[=11=])] }.bind(to: rightListTableView.rx.items(dataSource: listDataSource))
.disposed(by: rx.disposeBag)
}
private var lastIndex : NSInteger = 0
,用于比较左侧table视图的新旧选定索引,让右侧table视图开始,如果不同,则与currentIndex
使用了 self.currentSelectIndexPath
,因为当左侧 tableView 的最后一个单元格被点击时,UI 逻辑是不同的。
self.currentListData
用于捕获右侧table视图的当前数据,当左侧table视图点击不同行时。
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
}
}
我去掉了两个属性,用来和值通信的
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:[=10=])] }.bind(to: rightListTableView.rx.items(dataSource: listDataSource))
.disposed(by: rx.disposeBag)
// ······
的小费
lastIndex
与 currentIndex
配合使用,以确保当用户切换左侧 table 视图的 indexPath 时,右侧 table 视图始终位于顶部。
关键时刻,selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)
。
这应该在 table 视图更新结束时调用。
如果这可以用 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(())
}
}
然后处理table视图。
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
也是用来给对table查看初始数据的。
时刻也很重要。
这也可以在左侧 table 视图结束更新后完成
根据上面的代码,通过一些封装代码,很容易去掉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: [])
// ...
我是新来的Swifter,这是我新公司的代码。
使用RxSwift,使用RxDataSource,如何处理两个table视图关联?
点击了左侧 tableView 的单元格,右侧 tableView 的数据随之改变。
通过中间状态的变量组织右 table 视图的数据。
坏代码的味道。
这是图片
代码如下:
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:[=11=])] }.bind(to: rightListTableView.rx.items(dataSource: listDataSource))
.disposed(by: rx.disposeBag)
}
private var lastIndex : NSInteger = 0
,用于比较左侧table视图的新旧选定索引,让右侧table视图开始,如果不同,则与currentIndex
使用了 self.currentSelectIndexPath
,因为当左侧 tableView 的最后一个单元格被点击时,UI 逻辑是不同的。
self.currentListData
用于捕获右侧table视图的当前数据,当左侧table视图点击不同行时。
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
}
}
我去掉了两个属性,用来和值通信的
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:[=10=])] }.bind(to: rightListTableView.rx.items(dataSource: listDataSource))
.disposed(by: rx.disposeBag)
// ······
的小费
lastIndex
与 currentIndex
配合使用,以确保当用户切换左侧 table 视图的 indexPath 时,右侧 table 视图始终位于顶部。
关键时刻,selectRow(at: indexPath, animated: false, scrollPosition: UITableViewScrollPosition.none)
。
这应该在 table 视图更新结束时调用。
如果这可以用 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(())
}
}
然后处理table视图。
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
也是用来给对table查看初始数据的。
时刻也很重要。
这也可以在左侧 table 视图结束更新后完成
根据上面的代码,通过一些封装代码,很容易去掉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: [])
// ...