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

Swift中Recipe结构体布尔属性持久化失效问题求助

问题:食谱应用中布尔属性无法持久化的解决办法

我正在开发一款基于JSON食谱数据库的应用,能根据用户现有食材返回可用食谱。采用Recipe结构体作为数据模型,用MVVM模式,通过RecipeData这个ObservableObject保存和加载用户的追踪食谱。

相关代码实现

Recipe数据模型

struct Recipe: Codable, Identifiable, Hashable {
    // 遵循Identifiable协议所需
    var id = UUID()

    var isFavorite: Bool = false
    var isTracked: Bool = false
    var isComplete: Bool = false

    // 核心属性
    var title: String
    // 详细食材(例:1/2 汤匙糖)
    var ingredients: [String]
    var directions: [String]
    var link: String
    var source: String
    // 原始食材(例:糖)
    var NER: [String]

    // 仅解码JSON文件中包含的属性,其他属性(如id)会被忽略
    private enum CodingKeys: String, CodingKey { 
        case title, ingredients, directions, link, source, NER  
    } 
}

RecipeData数据管理类

class RecipeData: ObservableObject {
    @Published var recipes = [Recipe]()
    @Published var usersRecipes = [Recipe]()

    ...

    private var documentDirectory: URL {
      try! FileManager.default.url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
    }

    // 保存和加载用户追踪食谱的代码
    private var usersRecipesFile: URL {
        documentDirectory
            .appendingPathComponent("usersRecipes")
            .appendingPathExtension(for: .json)
    }

    func saveUsersRecipes() throws {
        let data = try JSONEncoder().encode(usersRecipes)
        try data.write(to: usersRecipesFile)
    }

    func loadUsersRecipes() throws {
      guard FileManager.default.isReadableFile(atPath: usersRecipesFile.path) else { return }
      let data = try Data(contentsOf: usersRecipesFile)
      usersRecipes = try JSONDecoder().decode([Recipe].self, from: data)
    }
}

MainTabView启动页

struct MainTabView: View {

    // 用于视图与模型交互的ViewModel
    @StateObject var recipeData: RecipeData = RecipeData()

    var body: some View {
        // 让用户在首页和收藏页之间切换的TabView
        TabView {
            HomeScreenView()
                .tabItem { Label("Home", systemImage: "house") }
            
            // 收藏页内容
            NavigationView {
                FavoritesView()
                    .navigationTitle("Favorites")
            }
            .tabItem { Label("Favorites", systemImage: "heart.fill") }
        }
        .environmentObject(recipeData)
        
        // 主TabView显示时加载用户食谱
        .onAppear {
            // try!表示如果出错直接崩溃应用
            try! recipeData.loadUsersRecipes()
        }
        
        // 用户食谱变化时自动保存
        .onChange(of: recipeData.usersRecipes) { _ in
            try! recipeData.saveUsersRecipes()
        }
    }
}

问题描述

将isTracked设为true的Recipe添加到usersRecipes后,当前能在追踪食谱列表正常显示,但关闭重启App后,这些Recipe的isTracked、isComplete、isFavorite均被重置为结构体定义的默认值false,而title、ingredients等其他属性能正常持久化。尝试过将布尔属性改为可选类型,重启后会重置为nil,问题仍未解决。如何让这三个布尔属性正常持久化?

附JSON示例条目:

{
    "": 0,
    "title": "No-Bake Nut Cookies",
    "ingredients": [
        "1 c. firmly packed brown sugar",
        "1/2 c. evaporated milk",
        "1/2 tsp. vanilla",
        "1/2 c. broken nuts (pecans)",
        "2 Tbsp. butter or margarine",
        "3 1/2 c. bite size shredded rice biscuits"
    ],
    "directions": [
        "In a heavy 2-quart saucepan, mix brown sugar, nuts, evaporated milk and butter or margarine.",
        "Stir over medium heat until mixture bubbles all over top.",
        "Boil and stir 5 minutes more. Take off heat.",
        "Stir in vanilla and cereal; mix well.",
        "Using 2 teaspoons, drop and shape into 30 clusters on wax paper.",
        "Let stand until firm, about 30 minutes."
    ],
    "link": "www.cookbooks.com/Recipe-Details.aspx?id=44874",
    "source": "Gathered",
    "NER": [
        "brown sugar",
        "milk",
        "vanilla",
        "nuts",
        "butter",
        "bite size shredded rice biscuits"
    ]
}

解决方案

  • 问题根源:你的CodingKeys枚举里没有包含isFavorite、isTracked、isComplete这三个布尔属性,导致JSON编码器在保存时会忽略它们,解码器加载时也不会读取这些字段,自然会使用结构体的默认值。

  • 核心修复步骤:

    1. 更新Recipe结构体的CodingKeys,把三个布尔属性加入枚举:
      private enum CodingKeys: String, CodingKey { 
          case title, ingredients, directions, link, source, NER, isFavorite, isTracked, isComplete  
      } 
      
    2. 由于你已经给布尔属性设置了默认值false,Swift的Codable会自动处理原始JSON中没有这些字段的情况——解码时如果JSON里没有对应字段,就使用默认值,不需要额外编写自定义的init(from decoder:)方法。
  • 额外优化建议:

    • 代码中的try!会导致App在出现错误时直接崩溃,建议替换为do-catch块处理错误,避免意外崩溃:
      .onAppear {
          do {
              try recipeData.loadUsersRecipes()
          } catch {
              print("加载用户食谱失败:\(error.localizedDescription)")
          }
      }
      
      .onChange(of: recipeData.usersRecipes) { _ in
          do {
              try recipeData.saveUsersRecipes()
          } catch {
              print("保存用户食谱失败:\(error.localizedDescription)")
          }
      }
      
    • 如果希望用户保存的食谱id在重启后保持一致,可以把id也加入CodingKeys,这样保存和加载时id会被持久化,避免每次重启生成新的UUID。

内容的提问来源于stack exchange,提问作者Eyan Yehya

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.23 05:54:40