我是新来的Swifter,这是我新公司的代码。
使用RxSwift,使用RxDataSource,如何处理两个table view关联?
左边tableView的cell被点击,右边tableView的数据跟着变化。
通过中间状态的变量组织右 TableView 的数据。
坏代码的味道。
这是图片
代码如下:
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的新旧索引,让右表View启动,用currentIndex
,如果不同
使用了 self.currentSelectIndexPath
,因为当左 tableView 的最终单元格被点击时,UI 逻辑是不同的。
self.currentListData
用于当左tableView点击不同行时,获取右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
}
}
最佳答案
很容易摆脱 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: [])
// ...
关于swift - 如何处理两个 TableView 关联?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55889255/