What should I do after receiving an NSUbiquityIdentityDidChangeNotification?
Asked Answered
H

1

6

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:]
Hydrolysate answered 1/12, 2013 at 21:47 Comment(0)
P
4

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];
}
Pacificas answered 1/12, 2013 at 22:10 Comment(7)
My app doesn't have its own use iCloud setting and will never have. User will enable / disable iCloud from the Settings app. So what should I do after receiving a NSUbiquityIdentityDidChangeNotification? Ask him what to do as in the Pages example and then migrate? Btw, thanks for the iCloud support you are giving to me these days!Hydrolysate
You can't migrate if the user turned iCloud off, because all you have access to is the fallback store. The user must turn iCloud back on in order to access the data, and then you would have to provide some other option - hence the need for an app specific setting. Note that the app specific setting is still set in the devices Settings app, its not a setting available in your app. You just include a Settings Bundle in your app and that will cause the iOS Settings app to create an entry specific to your app. Have a look in Settings for the Pages settings scroll down the first pagePacificas
Sorry, I do not have Pages. If I look in the Document & Settigns App, under iCloud -> Documents & Data, I already see an on/off switch for my App. Are you referring to it? This is created automatically and if the device is iCloud enabled it is automatically set as on, to me this also means I should not bother the user asking if he wants to use iCloud the first time it runs the app. I guess the iCloud design guides available from Apple are not updated!Hydrolysate
No, thats just a refinement of the global settings. Pages is free, download it. You will see the app settings on the same page as the iCloud option, just scroll down further. Yes their docs are not very current, they rejected my app until I did what Pages does.Pacificas
"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." OK I got what you meant, it is the same they do in Garage Band. Can you please post some code to show me how you react to keep/delete/continue to use iCloud?Hydrolysate
Sure, will find something for you tomorrow.Pacificas
Thanks pal, please add it to your answer so others can learn from it. In the while I am studying and implementing the setting bundle stuff. Now also the ask the user if should use iCloud starts to make sense!Hydrolysate

© 2022 - 2024 — McMap. All rights reserved.