Core data 管理对象和观察对象

Core data 管理对象和观察对象,core-data,observable,swiftui,nsmanagedobject,combine,Core Data,Observable,Swiftui,Nsmanagedobject,Combine,我正在尝试学习SwiftUI和核心数据(根本没有使用swift或objective C的经验),并通过编写一个跟踪健身房训练的应用程序来完成这项工作 我有一个锻炼实体,它与锻炼实体有一对多的关系 在我的应用程序中,我从从核心数据提取的nav视图中的训练列表开始。然后,我使用导航链接将选定的训练对象传递到显示训练对象属性的详细信息视图。“详细信息”视图包括训练名称、创建日期以及与该训练相关的训练列表。练习列表是从训练对象的“关系”属性加载的 接下来,我使用一个按钮将训练对象传递到具有训练列表的工作

我正在尝试学习SwiftUI和核心数据(根本没有使用swift或objective C的经验),并通过编写一个跟踪健身房训练的应用程序来完成这项工作

我有一个锻炼实体,它与锻炼实体有一对多的关系

在我的应用程序中,我从从核心数据提取的nav视图中的训练列表开始。然后,我使用导航链接将选定的训练对象传递到显示训练对象属性的详细信息视图。“详细信息”视图包括训练名称、创建日期以及与该训练相关的训练列表。练习列表是从训练对象的“关系”属性加载的

接下来,我使用一个按钮将训练对象传递到具有训练列表的工作表视图。锻炼列表是锻炼实体的一个请求,列表中与锻炼有关系的每个锻炼旁边都有一个复选标记。列表中的每一行都是另一个“行”视图的实例,整行都是一个按钮。用户可以点击行,将其添加/删除到训练中

到目前为止,这一切都非常有效。我的问题是,当我使用“训练列表表”视图更改训练与训练实体的关系时,“训练详细信息”视图不会更新。核心数据确实会更新,但关闭“练习表”视图时,“详细信息”视图不会刷新核心数据中更新的对象

如果我再返回一步到包含训练列表的父视图,并再次选择同一个训练对象,现在详细信息视图将使用核心数据中的正确训练列表刷新

我认为这是因为父训练列表正在使用获取请求来获取训练对象,所有子视图只是向下传递训练对象的一个实例,而不是监视它的更改。当我回到训练列表时,我猜fetch请求正在用最新数据替换对象实例

我希望“详细信息”视图能够在“练习表”视图中更改“关系”属性时实现,然后使用这些更改更新该详细信息视图上显示的练习列表

我怎样才能做到这一点?我尝试了通过视图层次结构的训练对象上的
@ObservedObject
,并且尝试了
managedObjectContext。当点击工作表视图上的“取消”按钮时刷新(训练,合并更改:true)
,但什么都没有

有什么想法吗

//
//  WorkoutDetail.swift


import SwiftUI

struct WorkoutDetail: View {

    @Environment (\.managedObjectContext) var moc

    @ObservedObject var workout: Workout
    @State private var workoutDate: Date
    @State private var exerciseArray: [Exercise]
    @State private var workoutName: String
    @State private var showingAddScreen = false
    var fetchRequest: FetchRequest<Workout>



    static let setDateFormat: DateFormatter = {
        let formatter = DateFormatter()
        formatter.dateFormat = "MMMM d, y, h:mm a"
        return formatter
    }()

    init(workout: Workout) {
        self.workout = workout
        // Define the initial form field values
        _workoutName = .init(initialValue: workout.wrappedWorkoutName)
        _workoutDate = .init(initialValue: workout.wrappedWorkoutDate)
        _exerciseArray = .init(initialValue: workout.workoutExerciseArray)
        let workoutPredicate = NSPredicate(format: "workoutName == %@", workout.workoutName!)
        fetchRequest = FetchRequest<Workout>(entity: Workout.entity(), sortDescriptors: [], predicate: workoutPredicate)

    }

    var body: some View {
            Form {
                Section(header: Text("Workout Name")) {
                    TextField("Workout Name", text: $workoutName)
                    Text("Created \(workoutDate, formatter: Self.setDateFormat)")

                    Button("Save Changes") {
                        self.workout.setValue(self.workoutName, forKey: "workoutName")
                        try? self.moc.save()
                    }
                }
                Section(header: Text("Exercises")) {
                    Button(action: {self.showingAddScreen.toggle()}) {
                    HStack {
                        Image(systemName: "pencil")
                        Text("Add/Remove Exercises")}
                    }
                    List{
                        ForEach(exerciseArray, id:\ .self) { exercise in
                            VStack(alignment: .leading) {
                                NavigationLink(destination: ExerciseSet(exerciseSetExercise: exercise, exerciseSetWorkout: self.workout)) {
                                    WorkoutExercise(exercise: exercise)
                                }
                            }
                        }
                    }
                }
            }.navigationBarTitle("\(workoutName)")
            .sheet(isPresented: $showingAddScreen) {
                AddExerciseToWorkout(workout: self.workout).environment(\.managedObjectContext, self.moc)}
        }
}

我可以通过将出现在“训练详细信息”视图中的练习列表放入另一个子视图来解决这个问题。我会将训练日期(每个训练的唯一ID)传递到子视图,然后在子视图上的
FetchRequest
中使用该日期作为
NSPredicate
,以提取分配给训练的训练的更新列表。 这是可行的,但感觉一定有更简单的方法

//
//  AddExerciseToWorkout.swift


import SwiftUI


struct AddExerciseToWorkout: View {
    @ObservedObject var workout: Workout
    @State private var exerciseArray: [Exercise]
    @State private var workoutName: String
    var exerciseNameArray: [String] = []
    @State var selectedExercises: [String] = []
    @FetchRequest(entity: Type.entity(), sortDescriptors: [NSSortDescriptor(key:"typeName", ascending: true)]) var strengthExercises: FetchedResults<Type>
    @Environment(\.presentationMode) var presentationMode


    init(workout: Workout) {
        self.workout = workout
        _exerciseArray = .init(initialValue: workout.workoutExerciseArray)
        _workoutName = .init(initialValue: workout.wrappedWorkoutName)
    }


    var body: some View {
        VStack {

            Text("\(workoutName) exercises:")
                .font(.headline)
                .padding()
            List{
                ForEach(strengthExercises, id:\.self) { strengthExercise in
                    Section(header: Text(strengthExercise.wrappedTypeName)) {
                        ForEach(strengthExercise.typeExerciseArray, id: \.self) { exercise in
                            AddExerciseToWorkoutRow(workout: self.workout, exercise: exercise)

                        }
                    }
                }
            }
            Button("Done") {self.presentationMode.wrappedValue.dismiss()
            }
        }
    }
}
//
//  AddExerciseToWorkoutRow.swift


import SwiftUI
import CoreData

struct AddExerciseToWorkoutRow: View {
    @Environment(\.managedObjectContext) var moc
    let exercise: Exercise
    @ObservedObject var workout: Workout
    @State private var exerciseArray: [Exercise]
    @State private var isSelected: Bool = false


    init(workout: Workout, exercise: Exercise) {
        self.workout = workout
        self.exercise = exercise
        _exerciseArray = .init(initialValue: workout.workoutExerciseArray)
        if exerciseArray.contains(exercise) {
            _isSelected = .init(initialValue: true)
        } else {
            _isSelected = .init(initialValue: false)
        }
    }


    var body: some View {
        Button(action: {
            if self.exerciseArray.contains(self.exercise) {
                self.workout.removeFromWorkoutToExercise(self.exercise)
                self.isSelected = false
            } else {
                self.workout.addToWorkoutToExercise(self.exercise)
                self.isSelected = true
            }
            try? self.moc.save()
                self.moc.refresh(self.workout, mergeChanges: true)
                self.workout.didChangeValue(forKey: "workoutToExercise")
        }) {
            HStack {
                Text(exercise.wrappedExerciseName)
                Spacer()
                if self.isSelected {
                    Image(systemName: "checkmark")
                        .foregroundColor(.green)
                }
            }
        }
    }
}