Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What should I do after receiving an NSUbiquityIdentityDidChangeNotification?

Apple documentation says we need to register for NSUbiquityIdentityDidChangeNotification and to compare the current iCloud token with the one previously stored in NSUserDefaults to detect if a user disabled iCloud from Documents & Data Settings or switched to another iCloud account.

I use a standard UIManagedDocument and I target iOS 7 so the fallback store is handled automatically by CoreData.

I do not understand what I should do after I find the user enabled / disabled iCloud or switched to another account. Should I migrate the persistent store? Or should I migrate it after an NSPersistentStoreCoordinatorStoresDidChangeNotification? Or should I never migrate it because everything is handled by CoreData?

After watching WWDC 2013 207 video several times I thought this would have been handled automatically by Core Data but I found that if I start with iCloud support and then I switch it off from Document & Data Settings and I insert new data, then I switch back iCloud to on, I end with two different data sets.

I would like that if I find the user disabled iCloud then the local db should contain up to the last change made until the iCloud was enabled and only from this point everything should stop syncing until iCloud will be enabled again.

In the WWDC 2013 207 video, in the Melissa demo, I also noticed a call to a method [self migrateBack] after an NSPersistentStoreCoordinatorStoresDidChangeNotification and this confuses me because the slides just show we should save our context here and refresh the UI, they do not show we should migrate anything:

**Account Changes Now**

NSPersistentStoreCoordinatorStoresWillChangeNotification
[NSManagedObjectContext save:]
[NSManagedObjectContext reset:] 

NSPersistentStoreCoordinatorStoresDidChangeNotification
[NSManagedObjectContext save:]
like image 675
nico9T Avatar asked Dec 01 '13 21:12

nico9T


1 Answers

The NSPersistentStoreCoordinatorStoresDidChangeNotification notification has nothing to do with changing the iCloud access.

If the user turns off iCloud access or logs out of iCloud then Core Data has to use the fallback store, which is probably empty! You won't get a chance to migrate anything.

However if the app has its own Use iCloud setting then you can test for that change and if set to NO migrate any documents to local storage assuming iCloud is still available.

You can see the expected behaviour in the latest version of Pages. If you have iCloud documents then as soon as you turn off iCloud Documents & Data in the Settings App you loose all access to the Pages documents. However if you go the Pages settings in the Settings App and turn off Use iCloud and then switch back to Pages you will be prompted to Keep on My iPhone, Delete from My iPhone or Keep using iCloud. That is how Apple will expect your app to work.

When the application enters the foreground we check the app specific iCloud settings and if they have changed we take the necessary action.

  1. If no change has been made then we keep running
  2. If the iCloud setting has changed then:
    • If it is turned OFF ask the user what to do
    • If it has been turned ON then share all documents in iCloud

/*! The app is about to enter foreground so use this opportunity to check if the user has changed any settings. They may have changed the iCloud account, logged into or out of iCloud, set Documents & Data to off (same effect as if they logged out of iCloud) or they may have changed the app specific settings. If the settings have been changed then check if iCloud is being turned off and ask the user if they want to save the files locally. Otherwise just copy the files to iCloud (don't ask the user again, they've just turned iCloud on, so they obviously mean it!)

@param application The application */

- (void)applicationWillEnterForeground:(UIApplication *)application
{
    //LOG(@"applicationWillEnterForeground called");

    // Check if the app settings have been changed in the Settings Bundle (we use a Settings Bundle which
    // shows settings in the Devices Settings app, along with all the other device settings).
    [[NSUserDefaults standardUserDefaults] synchronize];
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    bool userICloudChoice = [userDefaults boolForKey:_cloudPreferenceKey];


    // Now compare it with the current apps in memory setting to see if it has changed
    if (userICloudChoice  == useICloudStorage) {

        // No change so do nothing
        //LOG(@" iCloud choice has not changed");

    } else {

        // Setting has been changed so take action
        //LOG(@" iCloud choice has been changed!!");

        // iCloud option has been turned off
        if (!userICloudChoice) {

            //LOG(@" Ask user if they want to keep iCloud files locally ?");

            if ([[UIDevice currentDevice] userInterfaceIdiom] == UIUserInterfaceIdiomPhone) {
                _cloudChangedAlert = [[UIAlertView alloc] initWithTitle:@"You're not using iCloud" message:@"What would you like to do with documents currently on this phone?" delegate:self cancelButtonTitle:@"Keep using iCloud" otherButtonTitles:@"Keep on My iPhone", @"Delete from My iPhone", nil];
            } else {
                _cloudChangedAlert = [[UIAlertView alloc] initWithTitle:@"You're not using iCloud" message:@"What would you like to do with documents currently on this phone?" delegate:self cancelButtonTitle:@"Keep using iCloud" otherButtonTitles:@"Keep on My iPad", @"Delete from My iPad", nil];

            }

            [_cloudChangedAlert show];
            // Handle the users response in the alert callback

        } else {

            // iCloud is turned on so just copy them across... including the one we may have open

            //LOG(@" iCloud turned on so copy any created files across");
            [[CloudManager sharedManager] setIsCloudEnabled:YES];  // This does all the work for us
            useICloudStorage = YES;

        }
    }

}

- (void)alertView:(UIAlertView*)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex
{

    if (alertView == _cloudChoiceAlert)
    {
        //LOG(@" _cloudChoiceAlert being processed");
        if (buttonIndex == 1) {
            //LOG(@" user selected iCloud files");
            [[NSUserDefaults standardUserDefaults] setBool:YES forKey:_cloudPreferenceKey];
            [[NSUserDefaults standardUserDefaults] setValue:@"YES" forKey:_cloudPreferenceSet];
            useICloudStorage = YES;
            [[NSUserDefaults standardUserDefaults] synchronize];

            [[CloudManager sharedManager] setIsCloudEnabled:YES];
        }
        else {
            //LOG(@" user selected local files");
            [[NSUserDefaults standardUserDefaults] setBool:NO forKey:_cloudPreferenceKey];
            [[NSUserDefaults standardUserDefaults] setValue:@"YES" forKey:_cloudPreferenceSet];
            useICloudStorage = NO;
            [[NSUserDefaults standardUserDefaults] synchronize];

            [[CloudManager sharedManager] setIsCloudEnabled:NO];
        }
    }
    if (alertView == _cloudChangedAlert)
    {   //LOG(@" _cloudChangedAlert being processed");
        if (buttonIndex == 0) {
            //LOG(@" 'Keep using iCloud' selected");
            //LOG(@" turn Use iCloud back ON");
            [[NSUserDefaults standardUserDefaults] setBool:YES forKey:_cloudPreferenceKey];
            [[NSUserDefaults standardUserDefaults] synchronize];
            useICloudStorage = YES;
        }
        else if (buttonIndex == 1) {
            //LOG(@" 'Keep on My iPhone' selected");
            //LOG(@" copy to local storage");
            useICloudStorage = NO;
            [[CloudManager sharedManager] setDeleteICloudFiles:NO];
            [[CloudManager sharedManager] setIsCloudEnabled:NO];

        }else if (buttonIndex == 2) {
            //LOG(@" 'Delete from My iPhone' selected");
            //LOG(@" delete copies from iPhone");
            useICloudStorage = NO;
            [[CloudManager sharedManager] setDeleteICloudFiles:YES];
            [[CloudManager sharedManager] setIsCloudEnabled:NO];
        }
    }

}

/*! Checks to see whether the user has previously selected the iCloud storage option, and if so then check
    whether the iCloud identity has changed (i.e. different iCloud account being used or logged out of iCloud).
    If the user has previously chosen to use iCloud and we're still signed in, setup the CloudManager 
    with cloud storage enabled.
    If no user choice is recorded, use a UIAlert to fetch the user's preference.

 */
- (void)checkUserICloudPreferenceAndSetupIfNecessary
{
    FLOG(@"checkUserICloudPreferenceAndSetupIfNecessary called");

    [[CloudManager sharedManager] setFileExtension:_fileExtension  andUbiquityID:_ubiquityContainerKey ];

    id currentToken = [[NSFileManager defaultManager] ubiquityIdentityToken];

    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    NSString* userICloudChoiceSet = [userDefaults stringForKey:_cloudPreferenceSet];

    bool userICloudChoice = [userDefaults boolForKey:_cloudPreferenceKey];

    userICloudChoice = [userDefaults boolForKey:_cloudPreferenceKey];

    //FLOG(@" User preference for %@ is %@", _cloudPreferenceKey, (userICloudChoice ? @"YES" : @"NO"));

    if (userICloudChoice) {

        //LOG(@" User selected iCloud");
        useICloudStorage = YES;
        [self checkUbiquitousTokenFromPreviousLaunch:currentToken];

    } else {

        //LOG(@" User disabled iCloud");
        useICloudStorage = NO;

    }

    // iCloud is active
    if (currentToken) {

        //LOG(@" iCloud is active");

        // If user has not yet set preference the prompt for them to select a preference
        if ([userICloudChoiceSet length] == 0) {

            _cloudChoiceAlert = [[UIAlertView alloc] initWithTitle:@"Choose Storage Option" message:@"Should documents be stored in iCloud or on just this device?" delegate:self cancelButtonTitle:@"Local only" otherButtonTitles:@"iCloud", nil];
            [_cloudChoiceAlert show];

        }
        else if (userICloudChoice ) {

            [[CloudManager sharedManager] setIsCloudEnabled:YES];

        }
    }
    else {
        //LOG(@" iCloud is not active");
        [[CloudManager sharedManager] setIsCloudEnabled:NO];
        useICloudStorage = NO;
        [[NSUserDefaults standardUserDefaults] setBool:NO forKey:_cloudPreferenceKey];
        [[NSUserDefaults standardUserDefaults] synchronize];

        // Since the user is signed out of iCloud, reset the preference to not use iCloud, so if they sign in again we will prompt them to move data
        [userDefaults removeObjectForKey:_cloudPreferenceSet];
    }

    [self storeCurrentUbiquityToken:currentToken];
}
like image 119
Duncan Groenewald Avatar answered Oct 15 '22 06:10

Duncan Groenewald