Search code examples
ioscore-datacloudkitnsnotificationcenter

Registering to receive notifications of remote CloudKit changes not working


I have just finished setting up CoreData+CloudKit using the new iOS 13 NSPersistentCloudKitContainer. It works really nicely in that I can use auto-generated CoreData classes for property access and local storage, and the NSPersistentCloudKitContainer automatically synchronises changes between devices. The problem I am having is with getting notified of remote changes. I have checked the Apple documentation and this states that you tell the NSPersistentCloudKitContainer's NSPersistentStoreDescription that you want it to send the notification, and then register other objects as observers of this notification. I have done this and added a test method to show when remote changes were detected. The alert generated by the test method is never generated, but if I kill the app and re-open it, the changes are there immediately. So I believe the remote changes are being synchronised and integrated into the local CoreData storage, but the notification is not working. I have added the Background Modes entitlement to my target and selected the Remote notification mode. Code is below. Any help would be gratefully received!

Setting the option to send the notification:

- (NSPersistentCloudKitContainer *)persistentContainer {
    // The persistent container for the application. This implementation creates and returns a container, having loaded the store for the application to it.
    @synchronized (self) {
        if (_persistentContainer == nil) {
            _persistentContainer = [[NSPersistentCloudKitContainer alloc] initWithName:@"<redacted>"];
            [_persistentContainer loadPersistentStoresWithCompletionHandler:^(NSPersistentStoreDescription *storeDescription, NSError *error) {
                if (error != nil) {
                    // ...
                }
                else {
                    // ...

                    [storeDescription setOption:@(YES) forKey:NSPersistentStoreRemoteChangeNotificationPostOptionKey];

                    // ...
                }
            }];
        }
    }

    return _persistentContainer;
}

Registering to receive the notification:

- (void)viewDidLoad {
    [super viewDidLoad];

    // ...

    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(changes) name:NSPersistentStoreRemoteChangeNotification object:[CoreDataFunctions persistentContainer]];
}

Test method to respond to changes:

- (void)changes {
    UIAlertController *alert = [UIAlertController alertControllerWithTitle:@"Changes received" message:nil preferredStyle:UIAlertControllerStyleAlert];
    UIAlertAction *ok = [UIAlertAction actionWithTitle:@"OK" style:UIAlertActionStyleDefault handler:nil];
    [alert addAction:ok];
    [self presentViewController:alert animated:YES completion:nil];
}

Solution

  • Wherever you access your app’s persistent CloudKit container to grab the viewContext, you need to set the automaticallyMergesChangesFromParent property to true.

    lazy var managedContext: NSManagedObjectContext = {
        self.storeContainer.viewContext.automaticallyMergesChangesFromParent = true
        return self.storeContainer.viewContext
    }()
    

    Making this one-line change will enable the app (which is supported by NSFetchedResultsController) to update the UI in response to remote data changes…