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

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ş

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.22 10:15:04