[英]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. lastIndex
与currentIndex
,以确保当用户切换左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
}
}
}
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.