3

Is it possible to intercept if the user switches from on to off the iCloud support under Settings -> iCloud -> Document & Data?

Obviously when he does this the app has already resigned active and entered the background. I am targeting iOS7 and I would like to keep in sync the UIManagedDocument, otherwise it would be like having two different UIDocuments: one with iCloud support and all the data created until the switch from on to off and a new one without any data in it. If I create data when iCloud support has been switched to off, and then I switch back to on I get the same DB I had when the support has been switched to off.

Note: I believe nelico's answer is right. He wrote: "If your app is running and the user changes enables or disable Document & Data iCloud syncing via the settings app, your app will receive the SIGKILL signal."

When the user changes the settings the app is ALREADY in the background and receives the SIGKILL signal. This is what I do not understand and I do not want. Registering for NSUbiquityIdentityDidChangeNotification doesn't solve this problem.

nico9T
  • 2,496
  • 2
  • 26
  • 44
  • Because your DB is in local store anyway. If you want to strip out iCloud you have to use appropriate store options. You can do that if you cache ubiquitous key on disk and match it on each run, then do migration if not the same or nil. – pronebird Dec 27 '15 at 21:58

3 Answers3

3

Another, cleaner, solution is to listen for the NSUbiquityIdentityDidChangeNotification notification and when you get that notification then check the URLForUbiquityContainerIdentifier if it is null they either signed out or turned off 'Documents and Data'. You should also be tracking the current ubiquity token so that you can know not only if they logged off but if they changed iCloud accounts. It happens more than one might think because Apple Geniuses like to just create a new iCloud account when things go wrong on users devices.

ex:

id <NSObject,NSCopying,NSCoding> _currentUbiquityIdentityToken;

...

_currentUbiquityIdentityToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
[[NSNotificationCenter defaultCenter] addObserver: self selector: @selector (_iCloudAccountAvailabilityChanged:) name: NSUbiquityIdentityDidChangeNotification object: nil];

...

- (void)_iCloudAccountAvailabilityChanged:(NSNotification*)notif {
    if (![_currentUbiquityIdentityToken isEqual:[[NSFileManager defaultManager] ubiquityIdentityToken]]) {
        // Update the current token and rescan for documents.
        _currentUbiquityIdentityToken = [[NSFileManager defaultManager] ubiquityIdentityToken];
        // Do something about the change here...
    }
}
dtrotzjr
  • 928
  • 5
  • 18
  • Your right I did not see that last line of code in your entry. My response was more in response to the accepted answer. – dtrotzjr Nov 30 '13 at 17:27
  • I believe nelico's answer is right. He wrote: "If your app is running and the user changes enables or disable Document & Data iCloud syncing via the settings app, your app will receive the SIGKILL signal." When the user changes the settings the app is ALREADY in the foreground and receives the SIGKILL signal. This is what I do not understand and I do not want. Registering for NSUbiquityIdentityDidChangeNotification doesn't solve this problem. – nico9T Nov 30 '13 at 18:28
  • 2
    So then what is the purpose of `NSUbiquityIdentityDidChangeNotification` if the OS is just going to kill you anyway? It would seem if that is happening it is because of a bug with in the OS or in the app itself. – dtrotzjr Nov 30 '13 at 19:14
  • 1
    Yes this seems to be a new bug or feature and only seems to happen when connected to Xcode. The user can't change the settings in the Settings app with their App in the foreground, the app has to be switched to the background while they are in the Settings app, I usually deregister for notifications when my apps are switched to the background, can't recall the history of exactly why though. – Duncan Groenewald Nov 30 '13 at 21:25
  • I think too it's a bug. IOS7 and iCloud is very complicated because there is not sample code from Apple and the design guidelines updated to September 2013 are outdated. For example they say to ask the user if he wants to use iCloud the first time the app is run but when you install the app on the devices, if the user already uses iCloud it is turned on automatically so there is no need to bother him. And if he signs off then we can intercept NSUbiquityIdentityDidChangeNotification... But what to do then? I'll ask in another question. From WWDC 207 it seems we should do some kind of migration. – nico9T Dec 01 '13 at 18:49
1

Usually you would put the checks in the method below. That way whenever the app becomes active (including first time its launched) you can check the settings just prior to returning control to the user, no need to do polling in the background. For UIManagedDocument you may want to migrate the store to a local only copy and remove any iCloud content or the opposite, depending on the users input.

BTW just include the check in the previous answer to test if the global iCloud settings have been turned on or off. I don't do this because its only necessary to change the apps behaviour if the user has set the app specific settings.

/*! 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 settings have changed
    [[NSUserDefaults standardUserDefaults] synchronize];
    NSUserDefaults* userDefaults = [NSUserDefaults standardUserDefaults];
    bool userICloudChoice = [userDefaults boolForKey:_cloudPreferenceKey];

    // Check against the current in memory setting
    if (userICloudChoice  == useICloudStorage) {

        // The setting has not been changed so just ignore
        //LOG(@" iCloud choice has not changed");


    } else {

        // The setting has changed so do something
        //LOG(@" iCloud choice has been changed!!");

        // iCloud has been turned off so ask the user if they want to keep files locally
        if (!userICloudChoice) {
            //LOG(@" Ask user if  they want to keep iCloud files ?");

            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];

        } else {

            // iCloud has been turned on so just copy the files across, don't ask the user again...

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

        }
    }

}

Oh and also register for this notification in case the user logs on using another iCloud account.

[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(checkUserICloudPreferenceAndSetupIfNecessary) name:NSUbiquityIdentityDidChangeNotification object:nil];
Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76
  • Just to note, UIUserInterfaceIdiomPhone doesn't imply the device IS the iPhone. It can be an iPod Touch as well. – Indoor Mar 25 '14 at 10:28
0

It's not possible to intercept the changes, but you can programmatically check to see if they have iCloud enabled:

NSFileManager *fileManager = [NSFileManager defaultManager];
NSURL *ubiquityContainerURL = [fileManager URLForUbiquityContainerIdentifier:nil];
if (!ubiquityContainerURL) {
    // iCloud is not enabled
}

When your app enters the background, you could periodically poll this and have your app respond to any change in state.

EDIT: If your app is running and the user changes enables or disable Document & Data iCloud syncing via the settings app, your app will receive the SIGKILL signal. However, the OS can terminate your app for a number of reasons so trapping SIGKILL is not a reliable method of intercepting iCloud sync setting changes. You are still better off periodically polling.

neilco
  • 7,964
  • 2
  • 36
  • 41
  • 1
    Not totally accurate... you can know if the account has changed or been logged out of through the `NSUbiquityIdentityDidChangeNotification` notification - see my answer. – dtrotzjr Nov 30 '13 at 05:03