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

SwiftData单个视图@Query报错需设置modelContext的问题求助

SwiftData错误:“Set a .modelContext in view's environment to use Query”排查与解决

问题场景

已在主WindowGroup上绑定ModelContainer,其他视图使用@Query属性均正常,但SavedRecipesView下的RecipeList视图每次运行都会触发错误:Set a .modelContext in view's environment to use Query。

RecipeList与正常运行的Meals部分代码几乎一致,仅多了初始化displayedRecipes的逻辑(用于本地搜索过滤,避免重复查询)。

相关代码

主应用文件SwiftDataTestApp

@main
struct SwiftDataTestApp: App {
  var sharedModelContainer: ModelContainer = {
    let schema = Schema([Meal.self, Recipe.self])
    let modelConfiguration = ModelConfiguration(schema: schema, isStoredInMemoryOnly: false)
    do {
      return try ModelContainer(for: schema, configurations: [modelConfiguration])
    } catch {
      fatalError("Could not create ModelContainer: \(error)")
    }
  }()

  var body: some Scene {
    WindowGroup {
      ContentView()
    }
    .modelContainer(sharedModelContainer)
}

ContentView代码

struct ContentView: View {
  @Environment(\.modelContext) var modelContext // 非必需,仅保留原有代码
  @StateObject var state: AppState = AppState.shared()
  @State var selected: Int = 0

  var body: some View {
    TabView(selection: $selected) {
      HomeView()
        .tabItem {
          Label("Home", systemImage: "house.fill")
        }
        .tag(0)
      SavedMealsView()
        .tabItem {
          Label("Meals", systemImage: "square.and.arrow.down.on.square")
        }
        .tag(1)
      SavedRecipesView()
        .tabItem {
          Label("Recipes", systemImage: "list.bullet.circle")
        }
        .tag(2)
    }
  }
}

SavedRecipesView代码

struct SavedRecipesView: View {
  @Environment(NavigationContext.self) private var navigationContext
  @Environment(\.modelContext) private var modelContext // 非必需,仅保留原有代码

  var body: some View {
    @Bindable var navigationContext = navigationContext
    NavigationSplitView {
      MealTypeListView().navigationTitle("Saved Recipes")
    } content: {
      RecipeListView(mealType: navigationContext.selectedMealType)
        .navigationTitle("\(navigationContext.selectedMealType?.rawValue ?? "") Recipes")
    } detail: {
      NavigationStack {
        SavedRecipeView(recipe: navigationContext.selectedRecipe)
      }
    }
  }
}

RecipeListView及内部RecipeList代码

struct RecipeListView: View {
  let mealType: MealType?

  var body: some View {
    if let mealType {
      RecipeList(mealType: mealType)
    } else {
      ContentUnavailableView("Select A Meal Type", systemImage: "sidebar.left")
    }
  }
}
struct RecipeList: View {
  let mealType: MealType

  @Environment(NavigationContext.self) private var navigationContext
  @Environment(\.modelContext) private var modelContext
  @Query(sort: \Recipe.name) private var recipes: [Recipe]
 
  @State var searchText: String = ""
  @State var displayedRecipes: [Recipe]

  init(mealType: MealType) {
    self.mealType = mealType
    if mealType != .All {
      let predicate = #Predicate<Recipe> {
        $0.type == mealType.rawValue
      }
      _recipes = Query(filter: predicate, sort: \.name, order: .forward)
    }
    _displayedRecipes = State(initialValue: _recipes.wrappedValue) // 问题根源所在
  }
  var body: some View {
    // 展示recipes的代码
  }
}

问题根源

错误出在RecipeList的初始化方法中直接访问_recipes.wrappedValue:

  • 在视图的init阶段,实例尚未被插入到视图树中,环境变量(包括modelContext)还未注入到视图实例里。
  • @Query依赖环境中的modelContext来获取数据,此时访问其wrappedValue会导致SwiftData无法找到所需的modelContext,从而抛出错误。
  • Meals部分没有这个初始化步骤,因此不会触发该问题。

解决方案

方案1:改用计算属性处理搜索过滤

直接基于recipes和searchText生成过滤后的结果,无需提前存储displayedRecipes,既简化代码又能自动响应数据变化:

struct RecipeList: View {
  let mealType: MealType

  @Environment(NavigationContext.self) private var navigationContext
  @Environment(\.modelContext) private var modelContext
  @Query(sort: \Recipe.name) private var recipes: [Recipe]
 
  @State var searchText: String = ""

  // 计算属性动态生成过滤结果
  private var displayedRecipes: [Recipe] {
    let filteredByMealType = recipes
    if searchText.isEmpty {
      return filteredByMealType
    } else {
      return filteredByMealType.filter {
        $0.name.localizedCaseInsensitiveContains(searchText)
      }
    }
  }

  init(mealType: MealType) {
    self.mealType = mealType
    if mealType != .All {
      let predicate = #Predicate<Recipe> {
        $0.type == mealType.rawValue
      }
      _recipes = Query(filter: predicate, sort: \.name, order: .forward)
    }
    // 移除原有的displayedRecipes初始化代码
  }
  
  var body: some View {
    List(displayedRecipes) { recipe in
      // 你的视图展示代码
    }
    .searchable(text: $searchText)
  }
}

方案2:延迟初始化displayedRecipes

如果需要保留@State存储displayedRecipes,可以在视图出现后再赋值,并监听数据变化更新:

struct RecipeList: View {
  let mealType: MealType

  @Environment(NavigationContext.self) private var navigationContext
  @Environment(\.modelContext) private var modelContext
  @Query(sort: \Recipe.name) private var recipes: [Recipe]
 
  @State var searchText: String = ""
  @State var displayedRecipes: [Recipe] = [] // 初始化为空数组

  init(mealType: MealType) {
    self.mealType = mealType
    if mealType != .All {
      let predicate = #Predicate<Recipe> {
        $0.type == mealType.rawValue
      }
      _recipes = Query(filter: predicate, sort: \.name, order: .forward)
    }
  }
  
  var body: some View {
    List(displayedRecipes) { recipe in
      // 你的视图展示代码
    }
    .searchable(text: $searchText)
    .onAppear {
      // 视图出现时赋值,此时环境已就绪
      updateDisplayedRecipes()
    }
    .onChange(of: recipes) { _ in
      // 监听recipes数据变化,更新显示列表
      updateDisplayedRecipes()
    }
    .onChange(of: searchText) { _ in
      // 监听搜索文本变化,更新显示列表
      updateDisplayedRecipes()
    }
  }

  // 封装过滤逻辑
  private func updateDisplayedRecipes() {
    var result = recipes
    if !searchText.isEmpty {
      result = result.filter {
        $0.name.localizedCaseInsensitiveContains(searchText)
      }
    }
    displayedRecipes = result
  }
}

内容的提问来源于stack exchange,提问作者Neglected Sanity

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 09:14:51