CoreData with CloudKit on watchOS not syncing
Asked Answered
P

3

14

I have managed to get CoreData with CloudKit working with the new NSPersistentCloudKitContainer on the iOS version of my app and got it to automatically sync while the app is running. However, when I went to set things up on the watchOS app, I noticed that a sync will only occur if I force close and reopen the watch app.

import Foundation
import CoreData

class DataManager : NSObject {
    static let shared = DataManager()

    #if os(watchOS)
    let transactionAuthorName = "watchOSApp"
    #else
    let transactionAuthorName = "iOSApp"
    #endif

    override private init() {
        super.init()
    }

    lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentCloudKitContainer(name: "Model")

        let cloudStoreUrl = applicationDocumentDirectory()!.appendingPathComponent("product.sqlite")

        let cloudStoreDescription = NSPersistentStoreDescription(url: cloudStoreUrl)
        cloudStoreDescription.shouldInferMappingModelAutomatically = true
        cloudStoreDescription.shouldMigrateStoreAutomatically = true
        cloudStoreDescription.cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier:"iCloud.com.company.product")

        container.persistentStoreDescriptions = [cloudStoreDescription]

        container.loadPersistentStores(completionHandler: { storeDescription, error in
            if let error = error as NSError? {
                print("Error loading store. \(error)")
            }
        })

        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        container.viewContext.automaticallyMergesChangesFromParent = true

        try? container.viewContext.setQueryGenerationFrom(.current)
        container.viewContext.transactionAuthor = transactionAuthorName

        return container
    }()
}

// MARK: - Core Data
extension DataManager {

    func applicationDocumentDirectory() -> URL? {
        return FileManager.default.containerURL(forSecurityApplicationGroupIdentifier:"group.shiningdevelopers.h2o")
    }

    func managedObjectContext() -> NSManagedObjectContext {
        return persistentContainer.viewContext
    }

    func reset() {
        managedObjectContext().reset()
    }

    func saveContext () {
        let context = managedObjectContext()
        if context.hasChanges {
            do {
                try context.save()
            } catch let error as NSError {
                // Replace this implementation with code to handle the error appropriately.
                // Log this error for now to be able to glean more information
                print("Could not save. \(error), \(error.userInfo)")
            }
        }
    }
}

The following scenarios work - User has both the watchOS app and iOS app running - User makes a change to the data on the watch app - The change is reflected on the iOS app - User has only the iOS app running - User makes a change to the data on the iOS app - The user opens the watchOS app from terminated - The changes are reflected on the watchOS app

The following scenario does not work - User has the watchOS app running and the iOS app running - User makes a change to the data on the iOS app - No change ever makes it through to the watchOS app, even after waiting a long time - Only if I force close the app and restart does a sync occur

In the cases were a sync is successful I see the following logs correctly:

CoreData: debug: CoreData+CloudKit: 
-[PFCloudKitImporterZoneChangedWorkItem newMirroringResultByApplyingAccumulatedChanges:]_block_invoke_2(243): <PFCloudKitImporterZoneChangedWorkItem: 0x16530fb0> { ( "<CKRecordZoneID: 0x1656a920; zoneName=com.apple.coredata.cloudkit.zone, ownerName=__defaultOwner__>" ) } - Importing updated records: ( "<CKRecord: 0x16526280; recordType=CD_LogEntry, values={\n "CD_day" = 15;\n "CD_entityName" = LogEntry;\n "CD_glassesGoal" = 8;\n "CD_glassesLogged" = 13;\n "CD_lastModified" = "2019-09-15 18:56:08 +0000";\n "CD_month" = 9;\n "CD_year" = 2019;\n}, recordChangeTag=7d, recordID=2180D6A3-ACFC-4421-8CAF-6EE288DAAC2E:(com.apple.coredata.cloudkit.zone:defaultOwner)>" ) Deleted RecordIDs: {
}

In the last scenario, however, I don't see any logs, it's completely quiet. I am sharing the same CoreData/CloudKit code in both the iOS and watchOS versions of the app. I am also using a NSFetchedResultsController to make sure my UI stays up to date and it seems to be working on the iOS app but not the watchOS app. Not sure if there was some step that I had missed when setting up the watch extension.

Has anyone gotten syncing to watchOS to work? Any help would be appreciated.

Pvc answered 18/9, 2019 at 4:6 Comment(1)
Hi! Can you share sample how to sync CoreData between iPhone and Apple Watch? I try to make it, but all time get auth error (wrong bandle ID) on Watch side,Incur
S
3

Have you enabled context.automaticallyMergesChangesFromParent in your UI context?

let context = (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext

context.automaticallyMergesChangesFromParent = true
Sludgy answered 26/12, 2019 at 21:4 Comment(1)
Hi. Welcome to Stack Overflow. Thanks for taking the time! What you have written is not an answer to the question. Instead, it is another question. Nevertheless, it could be a valuable contribution, if you could describe how the change would effect the behavior of the context and how this change relates to the problem stated in the question. Otherwise, this should have been a comment, not an answer.Coercive
L
3

I had the same issue and I was able to solve it by configuring the default persistent store description instead of defining a new one:

lazy var persistentContainer: NSPersistentContainer = {
        let container = NSPersistentCloudKitContainer(name: "Model")
        // Change this variable instead of creating a new NSPersistentStoreDescription object
        guard let description = container.persistentStoreDescriptions.first else {
            fatalError("No Descriptions found")
        }

        let cloudStoreUrl = applicationDocumentDirectory()!.appendingPathComponent("product.sqlite")

        description.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
        description.setOption(true as NSObject, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
        description.url = cloudStoreUrl

        container.loadPersistentStores(completionHandler: { storeDescription, error in
            if let error = error as NSError? {
                print("Error loading store. \(error)")
            }
        })

        container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
        container.viewContext.automaticallyMergesChangesFromParent = true

        try? container.viewContext.setQueryGenerationFrom(.current)
        container.viewContext.transactionAuthor = transactionAuthorName

        return container
    }()
Laina answered 5/1, 2020 at 20:34 Comment(6)
Could you please post your entire code ? cause I am struggling with iCloudSync on watchOS and iOS.Mission
I'm not sure how to invite you to chat, but let's do that, that way you can tell me specifically what you want.Laina
Thanks for replying, I was struggling with cloudSync on Apple Watch, then realised from some reason cloud sync on simulators are blocked now.Mission
Yes, I came across the exact same issue with the watch simulator. With iPhone/iPad simulators, you can trigger a sync by moving the app in the background and back to the foreground.Laina
it's still not working. and I read it in apple forms that cloudSync is blocked in Watch simulator.Mission
Yes, watch simulator is unusable to test icloud sync. I was referring to being able to test iCloud sync on iPhone/iPad simulators only.Laina
D
1

Have you tried adding an observer like:

// Observe Core Data remote change notifications.
    NotificationCenter.default.addObserver(
        self, selector: #selector(type(of: self).storeRemoteChange(_:)),
        name: .NSPersistentStoreRemoteChange, object: container)

And then observing the observer for changes and doing UI updates at that time?

Depositary answered 20/10, 2019 at 16:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.