为何CoreData预加载生成重复条目而非唯一实体?
问题:CoreData预加载成就数据时生成重复条目
我想在视图首次加载且无成就条目时,向CoreData预加载成就数据,每个成就都是唯一的Achievements实体,后续用户达成成就时只更新completed属性。但现在虽然能生成正确数量的条目,却全是重复值。
实现代码
import SwiftUI import CoreData struct AchievementView: View { @Environment(\.managedObjectContext) private var viewContext @FetchRequest(entity: Achievements.entity(), sortDescriptors: [ NSSortDescriptor(key: "id", ascending: true)]) private var achievements: FetchedResults<Achievements> var body: some View { NavigationView { ZStack { Color("Background") .ignoresSafeArea(.all) VStack { HStack { let completedCount = achievements.filter(\.completed).count let totalCount = achievements.count let pctComp: Double = (Double(completedCount) / Double(totalCount)) * 100 Image(systemName: "star.circle").padding(.leading, 5) .foregroundColor(.secondary) Text("Achievements".uppercased()) .fontWeight(.heavy) .foregroundColor(.secondary) Spacer() Text("\(completedCount) OF \(totalCount) (\(pctComp, specifier: "%.f")%)") .font(.system(size:16)) .fontWeight(.heavy) .foregroundColor(.secondary) .padding(.trailing, 10) } ScrollView(showsIndicators: false) { VStack { ForEach(achievements) { achievement in HStack { VStack { if achievement.completed == false { Text(achievement.image ?? "") .padding() .grayscale(0.99) .overlay ( Circle() .stroke(Color(.systemGray6), lineWidth: 2) ).padding() } else { Text(achievement.image ?? "") .padding() .overlay ( Circle() .stroke(Color.green, lineWidth: 2) ).padding() } } VStack{ if achievement.completed == false { HStack { Text(achievement.name ?? "") .fontWeight(.heavy) .foregroundColor(Color(.systemGray2)) Spacer() } HStack { Text(achievement.desc ?? "") .fontWeight(.light) .font(.system(size:10)) .foregroundColor(Color(.systemGray2)) Spacer() } } else { HStack { Text(achievement.name ?? "") .fontWeight(.heavy) Spacer() } HStack { Text(achievement.desc ?? "") .fontWeight(.light) .font(.system(size:10)) Spacer() } } } Spacer() } .background(Color("WeekShowBack")) .cornerRadius(15) } } } }.padding() } }.navigationViewStyle(StackNavigationViewStyle()) .onAppear(perform: loadAchievements) } private func loadAchievements() { print("Number of Times Called") let achievementNames = ["First Entry", "Movie Starter"] let achievemnetDesc = ["Enter your first movie or show", "Track 10 Movies"] let achievementComp = [false, false] let achievementImage = ["🥇","🔟"] if achievements.count == 0 { for index in 0..<achievementNames.count { let newAchievement = Achievements(context: self.viewContext) newAchievement.name = achievementNames[index] newAchievement.desc = achievemnetDesc[index] newAchievement.completed = achievementComp[index] newAchievement.image = achievementImage[index] do { try viewContext.save() print("Achievement Loaded!!!!!!") } catch let error { print(error) } } } } }
问题截图

解决方案
问题根源
- 缺少唯一标识:
Achievements实体未设置唯一id属性,FetchRequest按id排序但该字段未赋值,导致CoreData无法区分不同实体,ForEach渲染时出现重复。 - 多次保存上下文:循环内每次创建实体都调用
save(),触发多次UI刷新,加剧了重复显示的问题。
修复步骤
给实体添加唯一
id
打开CoreData模型文件,给Achievements实体新增id属性:- 类型选择
UUID - 勾选「Use scalar type」选项
- 类型选择
修改预加载方法
调整loadAchievements,给每个新成就分配唯一id,并统一保存上下文:private func loadAchievements() { print("Number of Times Called") let achievementNames = ["First Entry", "Movie Starter"] let achievemnetDesc = ["Enter your first movie or show", "Track 10 Movies"] let achievementComp = [false, false] let achievementImage = ["🥇","🔟"] guard achievements.count == 0 else { return } for index in 0..<achievementNames.count { let newAchievement = Achievements(context: self.viewContext) newAchievement.id = UUID() // 赋值唯一ID newAchievement.name = achievementNames[index] newAchievement.desc = achievemnetDesc[index] newAchievement.completed = achievementComp[index] newAchievement.image = achievementImage[index] } // 循环结束后一次性保存 do { try viewContext.save() print("所有成就加载完成!!!!!!") } catch let error { print(error) } }让实体遵守
Identifiable协议
在Achievements+CoreDataProperties.swift中添加协议扩展:extension Achievements: Identifiable { }这样ForEach可以通过
id正确识别每个实体的唯一性,避免重复渲染。
内容的提问来源于stack exchange,提问作者user20715683
相关产品推荐
相关产品推荐

