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

SwiftUI:如何实现ViewModel跨视图访问以支持详情页收藏功能

解决SwiftUI DetailView中无法访问ViewModel的问题

核心问题分析

你遇到的Cannot find 'vm' in scope错误有两个关键原因:

  1. ViewModel作用域受限:你的ViewModel定义在ContentView的extension内部,导致DetailView无法访问这个类。
  2. 环境对象使用错误:@EnvironmentObject不需要手动初始化,且你注入的位置层级不够(加到了List上,应该在根视图层级注入)。

解决方案步骤

1. 调整ViewModel的作用域

把ViewModel从ContentView的extension中移出,改为独立的全局类,让所有视图都能访问:

import Foundation
import SwiftUI

final class ViewModel: ObservableObject{
    @Published var items = [Biase]()
    @Published var showingFavs = false
    @Published var savedItems: Set<Int> = [1, 7]
    
    var filteredItems: [Biase]  {
        showingFavs ? items.filter { savedItems.contains($0.id) } : items
    }
    
    private var biasStruct: BiasData = BiasData.allBias
    private var db = Database()
    
    init() {
        self.savedItems = db.load()
        self.items = biasStruct.biases
    }
    
    func sortFavs(){
        withAnimation { showingFavs.toggle() }
    }
    
    func contains(_ item: Biase) -> Bool {
        savedItems.contains(item.id)
    }
    
    func toggleFav(item: Biase) {
        if contains(item) {
            savedItems.remove(item.id)
        } else {
            savedItems.insert(item.id)
        }
        db.save(items: savedItems)
    }
}

2. 方法一:使用EnvironmentObject传递ViewModel

在ContentView的根视图层级注入环境对象,确保所有子视图(包括DetailView)能获取到:

struct ContentView: View {
    @StateObject private var vm = ViewModel()

    var body: some View {
        NavigationStack { // iOS 16+用NavigationStack,iOS 15及以下用NavigationView
            List(vm.filteredItems) { item in
                NavigationLink(destination: DetailView(entry: item)) {
                    Image(systemName: vm.contains(item) ? "heart.fill" : "heart")
                        .onTapGesture {
                            vm.toggleFav(item: item)
                        }
                    // 这里添加列表项的其他内容
                }
            }
        }
        .environmentObject(vm) // 在根视图层级注入环境对象
    }
}

修改DetailView,通过@EnvironmentObject接收ViewModel:

struct DetailView: View {
    let entry: Biase
    @EnvironmentObject var vm: ViewModel // 仅声明类型,无需初始化

    var body: some View {
        HStack{
            Image(systemName: vm.contains(entry) ? "heart.fill" : "heart")
                .onTapGesture {
                    vm.toggleFav(item: entry)
                }
            // 这里添加详情页的其他内容
        }
    }
}

方法二:直接传递ViewModel参数(更适合新手理解)

如果觉得环境对象逻辑复杂,可以直接把ViewModel作为参数传给DetailView:

// ContentView中的导航链接修改
NavigationLink(destination: DetailView(entry: item, vm: vm)) {
    // 列表项内容
}

// DetailView定义修改
struct DetailView: View {
    let entry: Biase
    @ObservedObject var vm: ViewModel // 直接接收ViewModel参数

    var body: some View {
        HStack{
            Image(systemName: vm.contains(entry) ? "heart.fill" : "heart")
                .onTapGesture {
                    vm.toggleFav(item: entry)
                }
        }
    }
}

内容的提问来源于stack exchange,提问作者John Citrowske

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 14:00:33