Swift MVC转MVVM后详情页数据传递失败问题求助
问题:MVVM架构下Segue跳转无法传递数据的原因与修复
核心原因
viewModel未初始化:DetailViewController的viewModel是可选类型(var viewModel: DetailViewModel?),在prepare(for:sender:)中尝试通过detailVC.viewModel?.xxx赋值时,viewModel处于nil状态,可选链调用会直接跳过,导致赋值完全无效。- ViewModel属性设计冲突:
DetailViewModel的pokemonId是非可选存储属性,必须在初始化时赋值,但原代码试图在初始化后修改它,不符合类的初始化逻辑。
修复方案
1. 提前初始化ViewModel并直接赋值
在prepare(for:sender:)中创建DetailViewModel实例,直接赋值给detailVC.viewModel,而非依赖未初始化的可选属性:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "showDetail", let detailVC = segue.destination as? DetailViewController, let indexPath = tableView.indexPathForSelectedRow { let pokemonId = "\(indexPath.row + 1)" // 初始化ViewModel,传入必填参数 let detailViewModel = DetailViewModel(pokemonId: pokemonId, apiService: PokeAPIService()) // 直接赋值给detailVC的viewModel detailVC.viewModel = detailViewModel // 传递初始数据 let pokemon = viewModel.pokemons[indexPath.row] detailViewModel.name = pokemon.name if let url = viewModel.getPokemonSpritesURL(for: indexPath.row) { detailViewModel.imageURL = url } } }
2. 优化ViewModel初始化逻辑(可选)
如果希望在初始化时就传入所有初始数据,可以扩展ViewModel的初始化方法,避免后续零散赋值:
class DetailViewModel { var pokemonId: String let apiService: PokeAPIService var name: String? var abilities: [Ability]? var imageURL: URL? // 扩展初始化方法,支持传入初始数据 init(pokemonId: String, apiService: PokeAPIService, name: String? = nil, imageURL: URL? = nil) { self.pokemonId = pokemonId self.apiService = apiService self.name = name self.imageURL = imageURL } // 原fetch方法保持不变 func fetchPokemonDetails(completion: @escaping () -> Void) { apiService.getPokemon(by: pokemonId) { [weak self] response in guard let self = self else { return } self.name = response.name self.abilities = response.abilities if let urlString = response.sprites?.frontDefault { self.imageURL = URL(string: urlString) } completion() } } }
对应prepare方法可以简化为:
override func prepare(for segue: UIStoryboardSegue, sender: Any?) { if segue.identifier == "showDetail", let detailVC = segue.destination as? DetailViewController, let indexPath = tableView.indexPathForSelectedRow { let pokemonId = "\(indexPath.row + 1)" let pokemon = viewModel.pokemons[indexPath.row] let detailViewModel = DetailViewModel( pokemonId: pokemonId, apiService: PokeAPIService(), name: pokemon.name, imageURL: viewModel.getPokemonSpritesURL(for: indexPath.row) ) detailVC.viewModel = detailViewModel } }
3. 确保ViewController触发UI配置
检查DetailViewController的viewDidLoad中是否调用了configureView(),否则即使ViewModel初始化完成,也不会触发数据获取和UI更新:
override func viewDidLoad() { super.viewDidLoad() configureView() }
内容的提问来源于stack exchange,提问作者LordGrim
相关产品推荐
相关产品推荐

