我正在尝试学习SwiftUI和核心数据(根本没有使用swift或objective C的经验),并通过编写一个跟踪健身房锻炼的应用程序来完成这一过程。
我有一个锻炼实体,它与一个锻炼实体有一对多的关系。
在我的应用程序中,我从从核心数据中提取的导航视图中的锻炼列表开始。然后,我使用一个导航链接将所选的Workout对象传递给一个详细信息视图,该视图显示了Workout对象的属性。详细信息视图包括锻炼名称、创建日期和与该锻炼相关的练习列表。练习列表是从Workout对象的relationship属性加载的。
接下来,我使用一个按钮将Workout对象传递给一个包含练习列表的工作表视图。锻炼列表是锻炼实体的提取请求,列表中与锻炼相关的每个锻炼旁边都有一个复选标记。列表中的每一行都是另一个“行”视图的实例,整行都是一个按钮。用户可以点击行以在锻炼中添加/删除它。
到目前为止,这一切都运行得很好。我的问题是,当我使用锻炼列表工作表视图更改锻炼与锻炼实体的关系时,锻炼详细信息视图不会更新。核心数据确实会更新,但在关闭练习表视图时,详细信息视图不会刷新核心数据中更新的对象。
如果我再后退一步,返回到带有锻炼列表的父视图,并再次选择相同的锻炼对象,现在详细信息视图将刷新为来自核心数据的正确锻炼列表。
我认为这是因为父锻炼列表使用fetch请求来获取锻炼对象,而所有子视图只是向下传递锻炼对象的单个实例,而不是监视它的更改。当我返回到锻炼列表时,我猜fetch request正在用最新的数据替换对象实例。
我希望详细信息视图能够意识到何时在练习表视图中更改了relationship属性,然后使用这些更改更新出现在该详细信息视图上的练习列表。
我怎么才能让它工作呢?我已经在通过视图层次结构传递的Workout对象上尝试了@ObservedObject,当点击工作表视图上的dismiss按钮时,我也尝试了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)
}
}
}
}
}发布于 2020-02-15 20:23:59
我能够通过将出现在Workout detail视图中的练习列表放入另一个子视图中来解决这个问题。我会将锻炼日期(每次锻炼的唯一ID )传递给子视图,然后在子视图的FetchRequest中使用该日期作为NSPredicate,以提取分配给锻炼的最新练习列表。它是有效的,但感觉肯定有一种更简单的方法。
https://stackoverflow.com/questions/60227260
复制相似问题