Swift中MVVM对于列表中Cell的拆分(下)

2,871 阅读4分钟

我正在参加「掘金·启航计划」

接着上一篇 Swift中MVVM对于列表中Cell的拆分(上) 继续分析余下的

1. ViewController

我之前使用的是对RxTableview的绑定,但是实际情况我们考虑数据itemts的动态变化,比如在某些功能在登录下和未登录下items是不一样的。或者权限不同展示不同的cell,因此实际上cell是灵活的最好的,虽然我这里是写死的。

image.png

我这里的写法还是按照之前开发经验,实际上并没有让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

我们继续看下ViewModletransform的实现,我们看下inputoutput的定义

image.png

我们要处理请求来决定我们的item,或者展示

image.png

我们把触发的序列合并为一个新的序列,方便我们订阅,这里我是把input中的viewWillAppear和用户信息,版本信息请求合并为一个新的序列进行订阅

image.png

处理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不同,当我们版本更新请求没有结果的时候

image.png

请求结果后,数据bind的列表也刷新了

image.png

当我们版本请求结果出来后发送结果,改变了数据源,从而刷新了绑定的列表

image.png

一些我们不用跳转的逻辑,比如打电话或者打开系统url,我们可以直接订阅处理

image.png

3. 总结

对于我们一个大的列表中我们可以区分把cell的一些行为绑定到cell的ViewModel中处理,从而拆分一部分我们的逻辑处理,这样我们cell的行为就由cell的ViewModel本身来维护。本质上来说是划分颗粒度,拆分我们控制器中ViewModel的逻辑

这里参考的 SwiftHub项目

iShot_2022-09-23_14.56.14.gif