Swift修改食材名称时重复校验失效问题求助
Swift食材名称唯一性校验问题修复
问题描述
开发食材管理功能时,在IngredientDetailView页面实现食材名称唯一校验,但修改现有食材名称为另一个已存在食材的名称时,校验逻辑无法生效,导致重复名称被保存。
问题原因
- 引用类型导致数据提前修改:
Ingredient原本是class(引用类型),在IngredientDetailView中修改ingredientItemData的属性时,会直接修改IngredientModel数组中的原对象。点击"Ok"校验时,原数组中已经包含修改后的名称,导致校验逻辑误判为只是修改了自身名称,而非重复。 - 校验逻辑未排除自身:原校验逻辑在判断名称是否存在时,没有排除当前正在修改的食材本身,导致无法准确识别与其他食材的重名情况。
修复方案
- 将
Ingredient从class改为struct(值类型),确保表单修改的是副本,不会影响原数据,直到确认保存时才更新。 - 重构校验逻辑,添加专门的判断函数,检查名称是否与其他食材重复(排除当前修改的食材)。
- 统一使用大小写不敏感的校验逻辑,避免大小写差异导致的漏判。
完整修改代码
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
相关产品推荐
相关产品推荐

