我正在参加「掘金·启航计划」
接着上一篇 Swift中MVVM对于列表中Cell的拆分(上) 继续分析余下的
1. ViewController
我之前使用的是对RxTableview
的绑定,但是实际情况我们考虑数据itemts
的动态变化,比如在某些功能在登录下和未登录下items是不一样的。或者权限不同展示不同的cell
,因此实际上cell是灵活
的最好的,虽然我这里是写死的。
我这里的写法还是按照之前开发经验,实际上并没有让viewModel来处理。首先对于一个tableview来说我们一般要输入下面的情况:
1. items
是动态变化的
2. cell
是否可点击是应该在viewModel
中处理
所以我们输入来说要一个触发条件
,比如下拉刷新,viewWillAppear
等。对于输出,我们输出要展示的items绑定到Tableview
,根据选择的cell,输出对应的SettingsSectionItem
。
struct Input {
/// 退出登录
var logoff: Driver<Void>
/// 刷新信息
let trigger: Observable<Void>
/// 点击cell
let selection: Driver<SettingsSectionItem>
}
struct Output {
/// 加载状态
var loadingSigal: Driver<Bool>
/// 退出登录结果
var logoff: Driver<Bool>
/// itets
let items: BehaviorRelay<[SettingsSection]>
/// 点击cell结果
let selectedEvent: Driver<SettingsSectionItem>
}
我们在绑定的方法中我们定义刷新的序列:
/// 刷新触发
let refresh = rx.viewWillAppear.mapToVoid()
mapToVoid
这个是一个拓展,转化为Void
类型序列
extension ObservableType {
func mapToVoid() -> Observable<Void> {
return map { _ in }
}
}
- 输入
let input = SettingViewModel.Input(logoff: self.settingView.logoutBtn.rx.tap.asDriver(), trigger: refresh, selection: settingView.tableView.rx.modelSelected(SettingsSectionItem.self).asDriver())
- tableview绑定
/// dataSource绑定tableview
let dataSource = RxTableViewSectionedReloadDataSource<SettingsSection>(configureCell: {
[weak self](dataSource, tab, indexPath, item) -> SettingCell in
let cell = self?.settingView.tableView.dequeueReusableCell(withIdentifier: SettingCell.description()) as! SettingCell
switch item {
case .userInfo(let viewModel),
.service(let viewModel),
.aboutPlatform(let viewModel),
.privacyPolicy(let viewModel):
cell.bind(to: viewModel)
return cell
case .update(viewModel: let viewModel, isUpdate: _ ,updateAddress: _):
cell.bind(to: viewModel)
return cell
}
})
根据不同的cell进行bind不同的viewModel
,这里的viewModel
替代了我们常用的model
。
- 绑定items
output.items.asObservable()
.bind(to:settingView.tableView.rx.items(dataSource: dataSource))
.disposed(by: disposeBag)
- 绑定点击事件
/// 页面跳转
output.selectedEvent.drive(onNext: { [weak self] (item) in
var controller: UIViewController? = nil
switch item {
case .userInfo:
controller = self?.navigator.get(segue: NavigatorMe.Scene.info)
case .aboutPlatform(let viewModel),
.privacyPolicy(let viewModel):
controller = WKWebViewController.init(url: MeAPI.webURL.webLinkURL(hospitalCode: ResourceSingle.shared.hospitalCode, smsCode: viewModel.code))
controller?.title = viewModel.title.value
default:break
}
if let nextController = controller {
self?.navigationController?.pushViewController(nextController, animated: true)
}
}).disposed(by: disposeBag)
针对不同事件进行跳转,通常我们在controller
中做一些绑定和路由跳转的实现。
- 一些自定义需求输出订阅
/// loadng框显示
output.loadingSigal
.drive { self.view.isLoading(isStop:$0) }
.disposed(by: disposeBag)
/// 退出登录
output.logoff.filter{$0}.drive(onNext: { _ in
UserManager.shared.cleanData()
BYNotificationCenter.shared.postNotificationLoginOut()
}).disposed(by: disposeBag)
在mmvm框架中,我们在控制器主要是进行绑定关系
以及路由跳转
。
2. ViewModel
我们继续看下ViewModle
的transform
的实现,我们看下input
和output
的定义
我们要处理请求来决定我们的item
,或者展示
我们把触发的序列合并为一个新的序列,方便我们订阅,这里我是把input
中的viewWillAppear
和用户信息,版本信息请求合并为一个新的序列进行订阅
处理items逻辑
refresh.map {(_) -> [SettingsSection] in
/// 处理items的逻辑,比如登录与否等,这里没有直接初始化
var items: [SettingsSection] = []
items += [
SettingsSection.setting(title: "", items: [SettingsSectionItem.userInfo(viewModel: SettingBadgeCellViewModel(with: "个人信息", detail: "基础信息", image: UIImage.getBundleImage(imageName:"me_settting_info"), hidesDisclosure: false, isUpdate: false))]),
SettingsSection.setting(title: "", items: [SettingsSectionItem.service(viewModel: SettingBadgeCellViewModel(with: "联系客服", detail: R.key.resource.phone, image: UIImage.getBundleImage(imageName:"me_settting_services"), hidesDisclosure: true, isUpdate: false,highlightColor: R.color.color10AA89))]),
SettingsSection.setting(title: "", items: [
SettingsSectionItem.aboutPlatform(viewModel: SettingBadgeCellViewModel(with: "关于平台", detail: "查看", image: UIImage.getBundleImage(imageName:"me_settting_info"), hidesDisclosure: false, isUpdate: false,code: "gywm")),
SettingsSectionItem.privacyPolicy(viewModel: SettingBadgeCellViewModel(with: "法律声明与隐私政策", detail: "查看", image: UIImage.getBundleImage(imageName:"me_settting_law"), hidesDisclosure: false, isUpdate: false,code: "flsmyszc"))]),
]
if let update = self.isNeedUpdate.value {
let updateViewModel = SettingsSectionItem.update(viewModel: SettingBadgeCellViewModel(with: "检查更新", detail: "", image: UIImage.getBundleImage(imageName:"me_settting_update"), hidesDisclosure: true, isUpdate: true,isNewest: self.isNeedUpdate.value),isUpdate: update, updateAddress: self.updateAddress.value)
let updateSection = SettingsSection.setting(title: "", items: [updateViewModel])
items.append(updateSection)
}
return items
}.bind(to: elements).disposed(by: cellDisposeBag)
这里随着我们条件序列不同,产生的items不同
,当我们版本更新请求没有结果的时候
请求结果后,数据bind
的列表也刷新了
当我们版本请求结果出来后发送结果,改变了数据源,从而刷新了绑定的列表
一些我们不用跳转的逻辑,比如打电话或者打开系统url,我们可以直接订阅处理
3. 总结
对于我们一个大的列表中我们可以区分把cell
的一些行为绑定到cell的ViewModel中处理
,从而拆分
一部分我们的逻辑处理,这样我们cell的行为就由cell的ViewModel
本身来维护。本质上来说是划分颗粒度,拆分我们控制器中ViewModel的逻辑
。
这里参考的 SwiftHub项目