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

SwiftUI实现收藏按钮后点击MenuItem出现Optional解包nil崩溃求助

问题根因

你遇到的崩溃由两个核心问题共同触发:

  1. 全局状态MenuModel从未被赋值,是空数组
    你在ContentView中单独解码JSON到本地menu变量用于列表渲染,没有把数据同步到全局@EnvironmentObject修饰的MenuModel的items数组中,所以在ItemDetail中从menuModel.items查找对应条目时必然返回nil,加上你声明itemIndex为隐式强制解包类型Int!,直接触发崩溃。
  2. ID解码存在隐患
    你的结构体中id默认赋值为UUID(),虽然Swift默认支持UUID字符串解码,但如果JSON中出现非法ID格式会直接解码失败,建议显式声明CodingKeys规避问题。

修复步骤

第一步:改造MenuModel,统一管理全局数据

final class MenuModel: ObservableObject {
    // 存储带分组的完整菜单数据,满足列表分组展示需求
    @Published var sections: [MenuSection] = []
    // 便捷访问所有平铺的菜单项
    var allItems: [MenuItem] { sections.flatMap(\.items) }
    
    init() {
        // 初始化时直接解码JSON到全局状态,不需要各个页面单独解码
        sections = Bundle.main.decode([MenuSection].self, from: "menu.json")
    }
}

第二步:修改ContentView,移除本地JSON解码逻辑,统一使用全局状态

struct ContentView: View {
    @EnvironmentObject var menuModel: MenuModel
    // 删掉本地解码的let menu = ... 代码,改用menuModel.sections
    
    @State private var searchString = ""
    
    var searchResult : [MenuSection] {
        if searchString.isEmpty { return menuModel.sections }
        return menuModel.sections.map { menuSection in
            var menuSearch = menuSection
            menuSearch.items = menuSection.items.filter { $0.name.lowercased().contains(searchString.lowercased()) }
            return menuSearch
        }.filter { !$0.items.isEmpty }
    }

    // 其余视图代码不变
}

第三步:修改ItemDetail,移除强制解包,安全获取Binding

删掉原来的itemIndex: Int!相关代码,改用安全的Binding查找逻辑:

struct ItemDetail: View {
    @EnvironmentObject var menuModel: MenuModel
    var item: MenuItem
    let colors: [String: Color] = ["E": .green, "M": .yellow, "D": .red]

    // 安全查找对应条目的isFavorite绑定
    private var favoriteBinding: Binding<Bool>? {
        for (sectionIdx, section) in menuModel.sections.enumerated() {
            if let itemIdx = section.items.firstIndex(where: { $0.id == item.id }) {
                return $menuModel.sections[sectionIdx].items[itemIdx].isFavorite
            }
        }
        return nil
    }

    var body: some View {
        ScrollView {
            VStack(){ 
                // 原有展示代码不变
            }
            .navigationTitle(item.name)
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .navigationBarTrailing) {
                    // 安全解包后再使用绑定,不会再触发崩溃
                    if let favoriteBinding = favoriteBinding {
                        FavoriteButton(isSet: favoriteBinding)
                    }
                }
            }
        }
    }
    // 其余代码不变
}

第四步(可选):给数据结构体添加显式CodingKeys,提升编解码稳定性

struct MenuItem: Hashable, Codable, Equatable, Identifiable {
    var id = UUID()
    var name: String
    var author: String
    var level: [String]
    var isFavorite: Bool
    var description: String
    
    // 显式指定需要编解码的字段,避免计算属性参与编解码
    enum CodingKeys: String, CodingKey {
        case id, name, author, level, isFavorite, description
    }

    // 其余计算属性、代码不变
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.26 20:24:01