sync not updating the view
Hi, Thanks for this nice project! Great work! 🙏 Awesome project when you want to test in a simple way. Thanks! Synchronization usually works, but sometimes gets stuck. My solution is to close and open the app again. (Then the view is updated.) Are there any solutions?
Hi again, After some testing I found a solution that seems to solve the "sync not updating the view"-problem. I addad some code in the persistentController, just above the loadPersistentStores. I am running the app on an iPad and an Iphone, and now the views are updating automatically on both devices. 😃 Is my code the best practice? (using stalenessInterval also solved the problem, but I don't think that's best practice. I think that it's better to make a viewContextMergePolicy.) I am using the viewContextMergePolicy from Apples Earthquakes examples. Plus "Pin the viewContext to the current generation token and set it to keep itself up to date with local changes." from Apples CoreDataCloudKitDemo. Here comes the code:
// - Tag: viewContextMergePolicy
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.undoManager = nil
container.viewContext.shouldDeleteInaccessibleFaults = true
// Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
I have the same symptoms on my app in development. Only after closing and reopening the app it updates. I applies above tow code snippets to the container in my datamanager class, but to no avail, still the same behaviour. Any other suggestions?
I needed to add
container.viewContext.automaticallyMergesChangesFromParent = true
This is not working for me. The object is saved when I exit the app or it's going in the background. Please share what you did to sync immediately after you create/update entitiy
This is my setup in my App file: ` @main struct UltimatePortfolioApp: App { @StateObject private var dataController: DataController
var body: some Scene {
WindowGroup {
ContentView()
//Add the container to the environment for SwiftUI to read Core Data values
.environment(\.managedObjectContext, dataController.container.viewContext)
//Also add the dataController to the environment, so we can use it's save and delete methods everywhere!
//So our own code can read Core Data values!
.environmentObject(dataController)
//call save whenever the app goes to the background
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willResignActiveNotification), perform: save)
}
}
init() {
let dataController = DataController()
_dataController = StateObject(wrappedValue: dataController)
}
//Save for when the app goes to the background
func save(_ note: Notification) {
dataController.save()
}
} `
And this the datamanager class I use:
` import CoreData import SwiftUI
/// An environment singleton responsible for managing out Core Data stack, including handling saving /// counting fetch requests, tracking awards and dealing with sample data. class DataController: ObservableObject { /// The lone CloudKit container to store all our data. let container: NSPersistentCloudKitContainer
/// Initialises a data controller, either in memory (for testing and previewing),
/// or on permanent storage, which is the default behaviour
/// - Parameter inMemory: Whether to store this data in temporary memory or not
init(inMemory: Bool = false) {
container = NSPersistentCloudKitContainer(name: "Main", managedObjectModel: Self.model)
// POSSIBLE SYNC FIX: - Tag: viewContextMergePolicy
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.undoManager = nil
container.viewContext.shouldDeleteInaccessibleFaults = true
container.viewContext.automaticallyMergesChangesFromParent = true
// POSSIBLE SYNC FIX: Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("###\(#function): Failed to pin viewContext to the current generation:\(error)")
}
// Persist data in memory only for previews and testing!
if inMemory {
container.persistentStoreDescriptions.first?.url = URL(fileURLWithPath: "/dev/null")
}
container.loadPersistentStores { storeDescription, error in
if let error = error {
fatalError("Fatal error loading persistent store: \(error.localizedDescription)")
}
}
}
///Creates an in memory only DataController with sample data for previews and testing
static var preview: DataController = {
let dataController = DataController(inMemory: true)
do {
try dataController.createSampleData()
} catch {
fatalError("failed to create preview data: \(error.localizedDescription)")
}
return dataController
}()
/// Prevent creating multiple instances of the Main container! For Example main app and Unit testing!
// This is not a computed property. The closure will run once and cache (store) the model
static let model: NSManagedObjectModel = {
guard let url = Bundle.main.url(forResource: "Main", withExtension: "momd") else {
fatalError("Failed to locate model file!")
}
guard let managedObjectModel = NSManagedObjectModel(contentsOf: url) else {
fatalError("Failed to load Main model!")
}
return managedObjectModel
}()
/// Delete all items to go back to a clean slate. For preview and testing purposes only !
func deleteAll() {
let fetchRequest1: NSFetchRequest<NSFetchRequestResult> = Item.fetchRequest()
let batchDeleteRequest1 = NSBatchDeleteRequest(fetchRequest: fetchRequest1)
_ = try? container.viewContext.execute(batchDeleteRequest1)
let fetchRequest2: NSFetchRequest<NSFetchRequestResult> = Project.fetchRequest()
let batchDeleteRequest2 = NSBatchDeleteRequest(fetchRequest: fetchRequest2)
_ = try? container.viewContext.execute(batchDeleteRequest2)
}
func save() {
if container.viewContext.hasChanges {
try? container.viewContext.save()
}
}
func delete(_ object: NSManagedObject) {
container.viewContext.delete(object)
}
///Custom count method on viewContext to handle nil coalescing.
///Returns the actual count or zero (0)
func count<T>(for fetchRequest: NSFetchRequest<T>) -> Int {
(try? container.viewContext.count(for: fetchRequest)) ?? 0
}
} `