Swift MVVM:如何让ViewModel操作全模型且TableView仅用UI模型
解决方案
核心思路是让ViewModel内部维护完整的原始数据模型数组,同时对外暴露一个由原始数据转换而来的UI模型序列,既保证ViewModel能操作完整数据,又让TableView只接触到简化的UI模型。
修改后的代码实现
struct MediaFile: Codable { var url: String var title: String var id: String var duration: TimeInterval var author: String var videoURL: URL var supportsVideo: Bool = false var videoDescription: String? var imgURL: URL? } struct MediaFileUIModel { var title: String var duration: TimeInterval var author: String init(mediaFile: MediaFile) { self.title = mediaFile.title self.duration = mediaFile.duration self.author = mediaFile.author } } class PlaylistViewModel: PlaylistViewModelProtocol { // 内部维护完整的原始数据,私有防止外部直接修改 private var mediaFiles: RxRelay.BehaviorRelay<[MediaFile]> = BehaviorRelay(value: []) // 对外暴露只读的UI模型序列,由原始数据自动转换 var uiModels: Observable<[MediaFileUIModel]> { mediaFiles.map { $0.map(MediaFileUIModel.init) } } var player: MusicPlayerProtocol var saver: MediaSaverProtocol init(player: MusicPlayerProtocol, saver: MediaSaverProtocol) { self.player = player self.saver = saver } func fetchData() { // 保存原始数据到内部的mediaFiles let fetchedFiles = saver.dataManager.fetchAllMedia() mediaFiles.accept(fetchedFiles) } // 示例:操作原始数据的方法(比如播放指定位置的媒体) func playMedia(at index: Int) { guard let media = mediaFiles.value[safe: index] else { return } player.play(url: media.url, videoURL: media.videoURL) } // 示例:删除原始数据 func deleteMedia(at index: Int) { var currentFiles = mediaFiles.value guard index < currentFiles.count else { return } currentFiles.remove(at: index) mediaFiles.accept(currentFiles) // 同步到数据持久层 saver.dataManager.saveMedia(currentFiles) } }
关键说明
- 原始数据私有存储:
mediaFiles设为私有,确保只有ViewModel能修改和操作完整的MediaFile模型,外部无法直接访问。 - UI模型自动转换:通过
map操作将原始数据序列转换成UI模型序列,TableView只需绑定uiModels这个Observable,完全感知不到原始数据的存在。 - 数据一致性:所有对数据的修改(比如删除、新增)都直接操作原始数据数组,修改后通过
accept更新Relay,UI模型会自动同步更新,无需手动维护两份数据。
TableView绑定示例
// 在TableViewController中 viewModel.uiModels .bind(to: tableView.rx.items(cellIdentifier: "MediaCell", cellType: MediaCell.self)) { _, uiModel, cell in cell.titleLabel.text = uiModel.title cell.durationLabel.text = "\(uiModel.duration)" cell.authorLabel.text = uiModel.author } .disposed(by: disposeBag) // 处理单元格点击,调用ViewModel的操作方法 tableView.rx.itemSelected .subscribe(onNext: { [weak self] indexPath in self?.viewModel.playMedia(at: indexPath.row) }) .disposed(by: disposeBag)
内容的提问来源于stack exchange,提问作者Rebigo
相关产品推荐
相关产品推荐

