ManagedObject 和 ObservedObject

Posted

技术标签:

【中文标题】ManagedObject 和 ObservedObject【英文标题】:ManagedObject and ObservedObject 【发布时间】:2020-02-14 13:26:07 【问题描述】:

我正在尝试学习 SwiftUI 和 Core Data(完全没有使用 swift 或 Objective C 的经验),并通过编写一个跟踪健身房锻炼的应用程序来完成它。

我有一个 Workout 实体,它与一个 Exercise 实体有一对多的关系。

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

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

到目前为止,这一切都很好。我的问题是,当我使用锻炼列表表视图更改锻炼与锻炼实体的关系时,锻炼详细信息视图不会更新。核心数据确实会更新,但当练习表视图关闭时,详细信息视图不会从核心数据刷新更新的对象。

如果我再返回到包含锻炼列表的父视图,并再次选择相同的锻炼对象,现在详细信息视图会刷新来自核心数据的正确锻炼列表。

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

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

我怎样才能做到这一点?我已经在通过视图层次结构传递的 Workout 对象上尝试了@ObservedObject,并且在点击工作表视图上的关闭按钮时我已经尝试了managedObjectContext.refresh(Workout, mergeChanges: 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)
        

//
//  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)
                
            
        
    

【问题讨论】:

【参考方案1】:

我能够通过获取锻炼详细信息视图中显示的锻炼列表并将其放入另一个子视图来解决此问题。我会将锻炼日期(这是每次锻炼的唯一 ID)传递给子视图,然后将该日期用作子视图上的 FetchRequest 内的 NSPredicate,以提取分配给锻炼的更新的锻炼列表。 它有效,但感觉肯定有更简单的方法。

【讨论】:

以上是关于ManagedObject 和 ObservedObject的主要内容,如果未能解决你的问题,请参考以下文章

核心数据:使用新创建的 ManagedObject 转至细节控制器

将 ManagedObject 转换为 NSObject

为啥existingObjectWithID 保存后会返回一个带temporaryID 的managedObject?

managedObject 上的 setValuesForKeysWithDictionary 在 swift 中给出编译错误

controllerDidChangeContent:每次在 Core Data 中创建 ManagedObject 时调用

保存后 ManagedObject 的上下文为零