SwiftUI:用户进入新视图时如何在不修改Core Data数据的前提下刷新训练练习数组
问题根因
- 你使用的
Exercise是Core Data生成的NSManagedObject,属于引用类型,直接将training.exercises赋值给tempExercises时仅复制对象引用,而非实际值。后续对tempExercises内的Exercise做修改(例如currentExercise.rep -= 1)会直接改动Core Data上下文内的原始对象,导致退出再进入时数据已被永久修改。 - 原有逻辑未在每次进入训练页时重新拉取Core Data原始数据,导致临时状态残留。
解决方案
1. 新增临时值模型,解耦Core Data引用
创建与Core Data Exercise字段对应的结构体,作为训练页临时使用的展示模型,完全规避引用类型的修改污染:
struct TempExercise: Identifiable { let id: UUID let name: String var rep: Int // 可变,适配临时调整逻辑 let rest: Int // 从Core Data原始Exercise对象初始化,仅读取值不持有引用 init(from coreDataExercise: Exercise) { self.id = coreDataExercise.id ?? UUID() self.name = coreDataExercise.name ?? "" self.rep = Int(coreDataExercise.rep) self.rest = Int(coreDataExercise.rest) } }
2. 改造TrainingView的状态逻辑
2.1 替换原有属性
移除直接持有Core Data Exercise数组的属性,仅保留临时值模型状态:
struct TrainingView: View { @Environment(\.presentationMode) var presentationMode: Binding<PresentationMode> // 保留Training引用仅用于读取训练基础信息,不再操作其关联的Exercise @State var training: Training // 临时模型数组,所有修改仅作用于此数组 @State private var tempExercises: [TempExercise] = [] @State private var timeRemaining = 0 @State private var timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() @State private var isTimerOn = false
2.2 新增视图出现时的重置逻辑
在.onAppear中从Core Data重新拉取原始练习数据,生成全新的临时模型数组,保证每次进入训练页都恢复初始状态:
// 原有body的VStack末尾追加/修改onAppear逻辑 .onAppear() { // 从Core Data原始数据生成全新临时数组,完全不保留引用 let originalExercises = training.exercises?.toArray() ?? [] tempExercises = originalExercises.map { TempExercise(from: $0) } updateBigTimer() }
2.3 调整临时数据操作方法
所有临时调整逻辑都基于值模型操作,完全不触碰Core Data对象:
private func startResting() { guard let currentExercise = tempExercises.first else { return } timeRemaining = currentExercise.rest startTimer() isTimerOn.toggle() } private func removeExercise() { guard !tempExercises.isEmpty else { return } let currentIndex = 0 var currentExercise = tempExercises[currentIndex] if currentExercise.rep == 1 { tempExercises.remove(at: currentIndex) } else if currentExercise.rep > 1 { // 值类型修改后直接替换数组元素即可,无引用污染 currentExercise.rep -= 1 tempExercises[currentIndex] = currentExercise } updateBigTimer() } private func updateBigTimer() { timeRemaining = tempExercises.first?.rest ?? 0 } private func stopTimer() { timer.upstream.connect().cancel() } private func startTimer() { timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect() }
3. 配套修改关联视图
3.1 调整ExerciseListView入参
将原有传入[Exercise]的逻辑替换为传入[TempExercise]:
struct ExerciseListView: View { @Binding var exercises: [TempExercise] var body: some View { List(exercises) { exercise in HStack { Text(exercise.name) Spacer() Text("剩余组数: \(exercise.rep)") } } } }
3.2 简化TrainingListView
移除未使用的Core Data上下文属性,避免误触发存储变更:
struct TrainingListView: View { @ObservedObject var training: Training var body: some View { NavigationLink(destination: TrainingView(training: training)) { HStack { Text(training.name ?? "") Text("\(training.exercises?.count ?? 0) exercises") } } } }
效果验证
- 所有临时调整(减组数、移除练习)仅作用于内存中的临时值数组,完全不会修改Core Data的原始存储
- 每次点击训练项进入训练页时,都会重新从Core Data拉取原始数据生成临时数组,自动恢复为初始的组数、练习列表状态
内容的提问来源于stack exchange,提问作者Hüseyin İyibaş
相关产品推荐
相关产品推荐

