SwiftUI列表视图在另一个视图更新Core Data实体后未更新

4

我在Core Data中存储了一个Lesson实体,其中之一的变量用于存储该课程是否已完成。

这些课程被列在SwiftUI列表中,当选中时进入游戏所在的视图。一旦游戏完成,完成变量就会更新为true。应该发生的是,列表视图会显示出带有勾号的游戏。

然而,现在出现的情况是当我在tapRight方法中保存课程的“已完成”状态时,会收到以下警告:

[TableView] Warning once only: UITableView was told to layout its visible cells and other contents without being in the view hierarchy (the table view or one of its superviews has not been added to a window).

然后,当我通过点击游戏视图顶部的导航按钮返回到列表视图时,我发现该游戏已经从列表中消失了。

但是,当我关闭应用程序并重新打开它时,列表会显示该游戏行及其勾号,因此我知道Core Data课程实例已经正确地更新。以下是列表视图的代码。

import SwiftUI
import CoreData
import UIKit

struct LessonList: View {

@Environment(\.managedObjectContext) var moc

@State private var refreshing = false
private var didSave =  NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave)

@FetchRequest(entity: Lesson.entity(), sortDescriptors: [], predicate: NSPredicate(format: "(type == %@) AND (stage == %@) AND ((complete == %@) OR (complete == %@))", "phonicIntro", "1", NSNumber(value: true), NSNumber(value: false) )) var phonicIntroLessons1: FetchedResults<Lesson>

var body: some View {

    NavigationView {      
        List {
            self.stage1Section
        }
        .navigationBarTitle("Lessons")
    }
}


private var phonicIntroLink : some View {
    ForEach(phonicIntroLessons1) { lesson in
        NavigationLink(destination: PhonicIntroGame(lesson: lesson)) {
            LessonRow(lesson: lesson)
        }
    }
}

private var stage1Section : some View {
    Section(header: Text("Stage 1")) {
        phonicIntroLink
    }.onReceive(self.didSave) { _ in
        self.refreshing.toggle()
    }
}

保存完成状态的游戏视图中的相关代码:

import SwiftUI
import AVFoundation

struct PhonicIntroGame: View {

@Environment(\.managedObjectContext) var moc

var lesson: Lesson?

func tapRight() {
    if ((self.lesson?.phonicsArray.count ?? 1) - 1) > self.index {
        self.index = self.index + 1
        print("This is index \(self.index)")
        //On completion
        if (index + 1)  == (lesson!.phonicsArray.count) {
            self.lessonComplete()
        }
    } else {
        print(self.index)

func lessonComplete() {
    self.lesson?.setComplete(true)
    self.saveLesson()
}

func saveLesson() {
    do {
        try moc.save()
    } catch {
        print("Error saving context \(error)")
    }
}

在 NSManagedObject 子类中:
extension Lesson {

func setComplete (_ state: Bool) {
    objectWillChange.send()
    self.complete = state
}
}

lessonRow的代码如下:

import SwiftUI
import CoreData

struct LessonRow: View {

@Environment(\.managedObjectContext) var moc
@State var refreshing1 = false
var didSave =  NotificationCenter.default.publisher(for: .NSManagedObjectContextDidSave)
var lesson: Lesson

var body: some View {
        HStack {
            Image(lesson.wrappedLessonImage )
                .resizable()
                .frame(width: 80, height: 80)
                .cornerRadius(20)

            Text(lesson.wrappedTitle)
                .font(.system(size: 20))
                .fontWeight(.thin)
                .padding()

            if lesson.complete == true {
                Image(systemName: "checkmark.circle")
                    .resizable()
                    .frame(width: 30, height: 30)
                    .foregroundColor(.green)
            } else {
                Rectangle()
                    .frame(width: 30, height: 30)
                    .foregroundColor(.clear)
            }
        }.padding()
            .onReceive(self.didSave) { _ in
                self.refreshing1.toggle()
            }


   }
}

我尝试过的方法:
  1. 使用通知和 .onReceive 强制重新加载列表。
  2. 使用 setComplete 函数设置 Lesson NSManagedObject 子类,该函数调用 'objectWillChange'。
  3. 通过为 complete 变量同时包含 true 和 false 来使 @FetchRequest 更详细。
我将非常感激任何解决此问题的建议。我是 SwiftUI 的新手,提前感谢您的帮助。

你的代码无法编译。请提供最小化的代码以展示问题,并能够进行编译和运行。这样更容易提供答案。 - Felix Marianayagam
感谢您的回复。我认为其他相关的代码只有lessonRow,我已经在上面添加了。我试图只保留相关内容,以免在问题中过多地提供信息。 - Zenman C
你的代码仍然无法编译。创建一个新项目,将此帖子中的代码复制并尝试编译。 - Felix Marianayagam
你能修好这个吗? - rv7284
是的,请参见下面最终是如何解决的。因为显然在评论中不能放两个@,所以只能作为答案发布。 - Zenman C
2个回答

6

是的,最终我解决了这个问题,方法是在PhonicIntroGame:View中的存储对象上添加@ObservedObject。具体如下 -

@ObservedObject var lesson: Lesson

谢谢,我为此挣扎了好几周。 - Misael Landeros
它可以工作!谢谢。 - Jiuhong Deng

-1

Core Data批量更新不会更新内存中的对象。您需要手动刷新。

批量操作绕过了正常的Core Data操作,直接在底层SQLite数据库(或支持持久存储的任何其他东西)上操作。它们这样做是为了提高速度,但这也意味着它们不会触发使用正常获取请求时获得的所有内容。

您需要执行类似于苹果公司的Core Data批处理编程指南中所示的操作:实现批量更新 - 执行后更新应用程序

原始答案 类似案例 类似案例


这位 OP 没有使用批量更新吗? - GazB

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接