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

为何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)
                }
            }
        }
    }
        
}

问题截图

成就列表重复显示的截图


解决方案

问题根源

  1. 缺少唯一标识:Achievements实体未设置唯一id属性,FetchRequest按id排序但该字段未赋值,导致CoreData无法区分不同实体,ForEach渲染时出现重复。
  2. 多次保存上下文:循环内每次创建实体都调用save(),触发多次UI刷新,加剧了重复显示的问题。

修复步骤

  1. 给实体添加唯一id
    打开CoreData模型文件,给Achievements实体新增id属性:

    • 类型选择UUID
    • 勾选「Use scalar type」选项
  2. 修改预加载方法
    调整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)
        }
    }
    
  3. 让实体遵守Identifiable协议
    在Achievements+CoreDataProperties.swift中添加协议扩展:

    extension Achievements: Identifiable {
    }
    

    这样ForEach可以通过id正确识别每个实体的唯一性,避免重复渲染。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.30 22:57:22