Search code examples
objective-ccore-dataios7icloud

Core Data with iCloud Notifications in iOS 7


I have added iCloud support to my app. when the user update his data I got a notification NSPersistentStoreDidImportUbiquitousContentChangesNotification

That's great

However, I have these problems

  1. I can't find a will version of this notification that I can use to show loader for example that the database will be updated
  2. Since I can't find a will version, then I used the same notification to display a loader to tell the user that the database is being updated ... The problem in this notification is that it can be called several time if the content changed in iCloud is relatively large which lead me to display several loading indicator !
  3. Is there a way that I can use to trigger the update through iCloud manually?

.


I added iCloud support to Core data by doing this:

in NSManagedObjectContext

_managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
_managedObjectContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;

in NSPersistentStoreCoordinator

[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:@{NSPersistentStoreUbiquitousContentNameKey:@"iCloudStore"} error:nil];

and In NSPersistentStoreDidImportUbiquitousContentChangesNotification notification method

- (void)persistentStoreDidImportUbiquitousContentChanges:(NSNotification *)notification
{
    NSManagedObjectContext *managedObjectContext = self.managedObjectContext;
    [managedObjectContext performBlock:^{
        [managedObjectContext mergeChangesFromContextDidSaveNotification:notification];

        dispatch_async(dispatch_get_main_queue(), ^{
            UIWindow *mainWindow = [[[UIApplication sharedApplication] delegate] window];
            MBProgressHUD *hud = [MBProgressHUD HUDForView:mainWindow];
            if (hud == nil) {
                hud = [[MBProgressHUD alloc] initWithWindow:mainWindow];
            }
            [mainWindow addSubview:hud];
            hud.labelText = NSLocalizedString(@"Progress updated", nil);
            [hud show:YES];
            [hud hide:YES afterDelay:1];

            // Post Notification
            [[NSNotificationCenter defaultCenter] postNotificationName:RESET_APPLICATION_NOTIFICATION object:nil];
        });
    }];
}

Solution

  • I can't find a will version of this notification that I can use to show loader for example that the database will be updated

    Yeah, that's because there isn't one. When new changes come in, the iCloud daemon saves them and then tells you what it has done.

    Since I can't find a will version, then I used the same notification to display a loader to tell the user that the database is being updated ... The problem in this notification is that it can be called several time if the content changed in iCloud is relatively large which lead me to display several loading indicator !

    Yes! I had this same issue. These notifications can be very frequent, so you don't want to notify the user every time one comes in. What I did was arrange to delay the notification until iCloud had quieted down for at least 1 second. You can do this using an NSTimer. First declare it as a property:

    @property (readwrite, retain) NSTimer *iCloudUpdateTimer;
    

    Then when you get the "did import" notification, do something like this:

    if (self.iCloudUpdateTimer == nil) {
        self.iCloudUpdateTimer = [NSTimer scheduledTimerWithTimeInterval:1.0
            target:self
            selector:@selector(notifyUser:)
            userInfo:nil
            repeats:NO];
    } else {
        [self.iCloudUpdateTimer setFireDate:[NSDate dateWithTimeIntervalSinceNow:1.0];
    }
    
    // Now actually handle the notification....
    

    What this does: Creates the timer if it doesn't exist. If it does exist, it delays the fire date for one second. When iCloud sends a lot of notifications, the timer keeps getting pushed out into the future. The timer will finally fire one second after iCloud calms down.

    Then when the timer fires, do something like:

    - (void)notifyUser:(NSNotification *)notification
    {
        [self.iCloudUpdateTimer invalidate];
        self.iCloudUpdateTimer = nil;
    
        // Then notify the user
    }
    

    You may need to use dispatch_async to make sure that all timer access happens on the same queue.

    (As an aside, even if there were a "will import" notification, it would probably be posted just as often as the current "did import" notification, so it wouldn't help you very much.)

    Is there a way that I can use to trigger the update through iCloud manually?

    No, you have absolutely no control over that.