You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 19:23:12