I have a Lesson entity stored in Core Data one of whose variables is to store whether the lesson is complete.
The lessons are listed in a SwiftUI list and when selected go to the View where the game exists. Once the game is completed, the complete variable is updated to true. And what is supposed to happen is that the list View displays the listed game with a check mark beside the game.
However, what is happening is that when I save the 'completed' state of the lesson in the game (in the tapRight method - see below), I get the warning:
[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).
And then when I return to the list View by pushing the navigation button at the top of the game View, I find the game has disappeared from the list.
However, when I close the app and reopen it, the list is there with the row for the game and the checkmark, so I know the core data lesson instance is being updated correctly. Code for List view below.
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()
}
}
Relevant code in game View for saving completed status:
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)")
}
}
In the NSManagedObject Subclass:
extension Lesson {
func setComplete (_ state: Bool) {
objectWillChange.send()
self.complete = state
}
}
The code for the lessonRow is as follows:
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()
}
}
}
The things I have tried:
- Forcing the list to reload with notifications and .onReceive
- Setting the Lesson NSManagedObject subclass with setComplete function, which calls 'objectWillChange'
- Making the @FetchRequest more detailed by including both true and false for the complete variable
I'd be grateful for any suggestions on how to resolve this. I'm new to SwiftUI so thanks in advance for all your help.