Search code examples
swiftcore-dataswiftuicloudkitwidgetkit

How to call WidgetCenter.shared.reloadAllTimelines() when Core Data in CloudKit changes?


I have an app that uses Core Data with CloudKit. Changes are synced between devices. Main target has Background Modes capability with checked Remote notifications, iCloud capability is checked with Services set to CloudKit and correct container in Containers checked.

How can I react in code to changes, deleting, and addition of records? I need to call WidgetCenter.shared.reloadAllTimelines() when Core Data in CloudKit changes to update iOS 14 homescreen widget.

My goal is to get this working: I change/add/delete record on icloud.developer.apple.com or another device, and WidgetCenter.shared.reloadAllTimelines() called to display correct data in widget. App may be in background or foreground.

From AppDelegate.swift:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
    // Register for Remote Notifications
    application.registerForRemoteNotifications()
    
    return true
}

Also, noticed messages in output log:

CoreData: debug: CoreData+CloudKit: -[NSCloudKitMirroringDelegate remoteStoreDidChange:]_block_invoke(2138): <NSCloudKitMirroringDelegate: 0x281818d00> - Ignoring remote change notification because the exporter has already caught up to this transaction: 64 / 64 - <NSSQLCore: 0x100b09440> (URL: file:///var/mobile/Containers/Data/Application/F83C68DA-7C36-42CC-926D-7C721C679579/Library/Application%20Support/AppTitle.sqlite)


Solution

  • If you want to subscribe to Core Data remote notifications you can use onReceive:

    struct WidgetEntryView : View {
        var entry: Provider.Entry
    
        var body: some View {
            Text(entry.date, style: .time)
                .onReceive(NotificationCenter.default.publisher(for: .NSPersistentStoreRemoteChange)) { _ in
                    // make sure you don't call this too often
                    WidgetCenter.shared.reloadAllTimelines()
                }
        }
    }
    

    Just make sure you don't call reloadAllTimelines() too often - there likely is a limited number of updates available to your widget.