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

Swift修改食材名称时重复校验失效问题求助

Swift食材名称唯一性校验问题修复

问题描述

开发食材管理功能时,在IngredientDetailView页面实现食材名称唯一校验,但修改现有食材名称为另一个已存在食材的名称时,校验逻辑无法生效,导致重复名称被保存。

问题原因

  1. 引用类型导致数据提前修改:Ingredient原本是class(引用类型),在IngredientDetailView中修改ingredientItemData的属性时,会直接修改IngredientModel数组中的原对象。点击"Ok"校验时,原数组中已经包含修改后的名称,导致校验逻辑误判为只是修改了自身名称,而非重复。
  2. 校验逻辑未排除自身:原校验逻辑在判断名称是否存在时,没有排除当前正在修改的食材本身,导致无法准确识别与其他食材的重名情况。

修复方案

  1. 将Ingredient从class改为struct(值类型),确保表单修改的是副本,不会影响原数据,直到确认保存时才更新。
  2. 重构校验逻辑,添加专门的判断函数,检查名称是否与其他食材重复(排除当前修改的食材)。
  3. 统一使用大小写不敏感的校验逻辑,避免大小写差异导致的漏判。

完整修改代码

1. 修改Ingredient为值类型struct

struct Ingredient: Identifiable, Codable {
    var id: String?
    var nom = ""
    var metric = ""
    
    init() {}
    
    init(id: String, nom: String, metric: String) {
        self.id = id
        self.nom = nom
        self.metric = metric
    }
}

2. 优化IngredientDetailView的校验逻辑

struct IngredientDetailView: View {
    @Environment(\.dismiss) private var dismiss
    
    @State var ingredientItemData: Ingredient
    @EnvironmentObject var ingredientsVM: IngredientModel
    @State private var showingAlertIngredientExist = false
    let metrics: [String] = [
        "Unité", "kg", "g", "l", "ml"
    ]
    
    // 判断名称是否重复(排除当前食材)
    private func isNameDuplicate() -> Bool {
        let lowercasedNewName = ingredientItemData.nom.lowercased()
        return ingredientsVM.ingredients.contains { ingredient in
            ingredient.id != ingredientItemData.id && ingredient.nom.lowercased() == lowercasedNewName
        }
    }
    
    var body: some View {
        List {
            TextField("Ingredient name", text: $ingredientItemData.nom)
            Text("Metric").bold()
            Picker(selection: $ingredientItemData.metric, label: Text("Metric")) {
                ForEach(metrics, id: \.self) { item in
                    Text(item)
                }
            }.pickerStyle(.segmented)
        }
        .toolbar {
            ToolbarItem(placement: .navigationBarLeading) {
                Button("Cancel") {
                    dismiss()
                }
            }
            ToolbarItem(placement: .navigationBarTrailing) {
                Button("Ok") {
                    if isNameDuplicate() {
                        showingAlertIngredientExist.toggle()
                    } else {
                        ingredientsVM.add(ing: ingredientItemData)
                        dismiss()
                    }
                }
                .alert(isPresented: $showingAlertIngredientExist) {
                    Alert(title: Text("食材已存在"), message: Text("已有同名的食材!"), dismissButton: .default(Text("确定")))
                }
            }
        }
        .navigationBarBackButtonHidden()
        .navigationBarTitleDisplayMode(.inline)
    }
}

struct IngredientDetailView_Previews: PreviewProvider {
    static var previews: some View {
        IngredientDetailView(ingredientItemData: Ingredient())
            .environmentObject(IngredientModel())
    }
}

3. 其他代码保持不变

struct IngredientListView: View {
    @Environment(\.dismiss) private var dismiss
    
    @State private var sheetIsPresented = false
    
    @EnvironmentObject var ingredientVM: IngredientModel
        
    var body: some View {
        NavigationStack {
            List {
                ForEach(ingredientVM.ingredients) { item in
                    NavigationLink {
                        IngredientDetailView(ingredientItemData: item)
                    } label: {
                        Text(item.nom)
                    }
                }
                .onDelete { indexSet in
                    ingredientVM.ingredients.remove(atOffsets: indexSet)
                    ingredientVM.saveData()
                }
            }
            .navigationTitle("食材列表")
            .navigationBarTitleDisplayMode(.automatic)
            .toolbar {
                ToolbarItem(placement: .navigationBarLeading) {
                    EditButton()
                }
                ToolbarItem(placement: .navigationBarTrailing) {
                    Button {
                        sheetIsPresented.toggle()
                    } label: {
                        Image(systemName: "plus")
                    }
                }
            }
            .sheet(isPresented: $sheetIsPresented) {
                NavigationStack {
                    IngredientDetailView(ingredientItemData: Ingredient())
                }
                .presentationDetents([.large, .medium, .fraction(0.75)])
            }
        }
    }
}

struct IngredientListView_Previews: PreviewProvider {
    static var previews: some View {
        IngredientListView()
            .environmentObject(IngredientModel())
    }
}

class IngredientModel: ObservableObject {
    @Published var ingredients: [Ingredient] = []
    let saveKey = "Ingredients"
    
    init() {
        loadData()
    }
    
    func add(ing: Ingredient) {
        if ing.id == nil {
            var newIngredient = ing
            newIngredient.id = UUID().uuidString
            ingredients.append(newIngredient)
        } else {
            if let index = ingredients.firstIndex(where: {$0.id == ing.id}) {
                ingredients[index] = ing
            }
        }
        saveData()
    }
    
    func loadData() {
        if let data = UserDefaults.standard.data(forKey: saveKey) {
            if let decoded = try? JSONDecoder().decode([Ingredient].self, from: data) {
                ingredients = decoded
                return
            }
        }
    }
    
    func saveData() {
        if let encoded = try? JSONEncoder().encode(ingredients) {
            UserDefaults.standard.set(encoded, forKey: saveKey)
        }
    }
}

修复说明

  • 改为值类型后,表单修改的是Ingredient的副本,不会提前污染原数组数据,确保校验时能基于原始数据判断是否重名。
  • 提取的isNameDuplicate()函数统一处理添加和修改场景:添加时,ingredientItemData.id为nil,会检查所有食材是否有同名;修改时,会排除当前食材本身,只检查其他食材是否有同名。
  • 全流程使用小写比较,彻底避免大小写差异导致的重名漏判。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.09 22:40:40