我是一个新的 Swifter,这是我新公司的代码。
使用 RxSwift,使用 RxDataSource,如何处理两个表视图关联?
左边tableView的cell被点击了,右边tableView的数据随之变化。
通过中间状态变量组织右表视图的数据。
坏代码的味道。</p>
这是图像
代码如下:</p>
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
, 用于比较左表视图的新旧选定索引,让右表视图启动,currentIndex
如果不同
被使用了self.currentSelectIndexPath
,因为当左 tableView 的最后一个单元格被点击时,UI 逻辑是不同的。
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
}
}