iCloud and UIDocument: Beyond the Basics, Part 3/4

This is a blog post by site administrator Ray Wenderlich, an independent software developer and gamer. Welcome back to our document-based iCloud app tutorial series! In this tutorial series, we are making a complete document-based iCloud app called PhotoKeeper, with features that go beyond just the basics. In the first and second parts of the […] By Ray Wenderlich.

Leave a rating/review
Save for later
Share
You are currently viewing page 2 of 4 of this article. Click here to view the first page.

iCloud On, iCloud Off

Like we mentioned back in part 1, you shouldn’t just assume you should use iCloud if it’s available – you should allow the user to enable/disable iCloud for your app.

And also like we discussed before, I believe the best place for this configuration option is in Settings (rather than in-app settings). This way it reduces clutter in your app and discourages users from changing it unnecessarily.

Adding a switch to turn iCloud on and off is easy, so let’s deal with that first. Create a new file with the iOS\Resource\Settings Bundle template, and name it Settings.bundle. Open Settings.bundle\Root.plist, and modify the file to look like the following:

Configuring the Settings.bundle plist to add a switch

Compile and run the app on your device, and switch to the Settings app. Scroll down to find the category for PhotoKeeper, and you should see a switch for iCloud:

iCloud On/Off Switch in Settings

Settings is pretty cool because getting the value for this switch is as easy as reading the “iCloudOn” key from NSUserDefaults.

Now that we’ve got a switch, it starts getting tricky. We have to make sure we have code that handles all of the following cases:

  • If iCloud isn’t switched on, but it’s available (and we haven’t bugged the user already), ask the user if they want to turn on iCloud. This way they don’t necessarily have to go to Settings at all.
  • If iCloud isn’t available, but it was on before, warn the user that although the app can’t access their iCloud files anymore, they’re still out on the cloud.
  • If iCloud was switched on (but wasn’t previously), that means we need to move our local files to iCloud.
  • If iCloud was switched off (but wasn’t previously), that means we need to ask the user what to do with the old iCloud files. The user might want to leave them on iCloud, keep a copy, or change their mind and turn iCloud back on again.

To accomplish all this, in addition to the “iCloudOn” flag managed by Settings, we need to create two more NSUserDefaults flags – one for if iCloud “used to be on”, and one for if we’ve prompted the user if they want to use iCloud yet.

Add these wrappers to PTKMasterViewController.m at the top of the “Helpers” section (overwriting the old iCloudOn method):

- (BOOL)iCloudOn {    
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"iCloudOn"];
}

- (void)setiCloudOn:(BOOL)on {    
    [[NSUserDefaults standardUserDefaults] setBool:on forKey:@"iCloudOn"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

- (BOOL)iCloudWasOn {    
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"iCloudWasOn"];
}

- (void)setiCloudWasOn:(BOOL)on {    
    [[NSUserDefaults standardUserDefaults] setBool:on forKey:@"iCloudWasOn"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

- (BOOL)iCloudPrompted {
    return [[NSUserDefaults standardUserDefaults] boolForKey:@"iCloudPrompted"];
}

- (void)setiCloudPrompted:(BOOL)prompted {    
    [[NSUserDefaults standardUserDefaults] setBool:prompted forKey:@"iCloudPrompted"];
    [[NSUserDefaults standardUserDefaults] synchronize];
}

Then make the following changes to PTKMasterViewController.m:

// Add some stub methods to the bottom of the "File management" section
- (void)iCloudToLocal {
    NSLog(@"iCloud => local");
}

- (void)localToiCloud {
    NSLog(@"local => iCloud");
}

#pragma mark iCloud Query

- (void)startQuery {        
    NSLog(@"Starting to watch iCloud dir...");
}

// Replace the "TODO" in refresh with the following
if (!_iCloudAvailable) {
    
    // If iCloud isn't available, set promoted to no (so we can ask them next time it becomes available)
    [self setiCloudPrompted:NO];
    
    // If iCloud was toggled on previously, warn user that the docs will be loaded locally
    if ([self iCloudWasOn]) {
        UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"You're Not Using iCloud" message:@"Your documents were removed from this iPad but remain stored in iCloud." delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
        [alertView show];
    }
    
    // No matter what, iCloud isn't available so switch it to off.
    [self setiCloudOn:NO]; 
    [self setiCloudWasOn:NO];
    
} else {        
    
    // Ask user if want to turn on iCloud if it's available and we haven't asked already
    if (![self iCloudOn] && ![self iCloudPrompted]) {
        
        [self setiCloudPrompted:YES];
        
        UIAlertView * alertView = [[UIAlertView alloc] initWithTitle:@"iCloud is Available" message:@"Automatically store your documents in the cloud to keep them up-to-date across all your devices and the web." delegate:self cancelButtonTitle:@"Later" otherButtonTitles:@"Use iCloud", nil];
        alertView.tag = 1;
        [alertView show];
        
    } 
    
    // If iCloud newly switched off, move local docs to iCloud
    if ([self iCloudOn] && ![self iCloudWasOn]) {                    
        [self localToiCloud];                                                           
    }                
    
    // If iCloud newly switched on, move iCloud docs to local
    if (![self iCloudOn] && [self iCloudWasOn]) {
        [self iCloudToLocal];                    
    }
    
    // Start querying iCloud for files, whether on or off
    [self startQuery];
    
    // No matter what, refresh with current value of iCloudOn
    [self setiCloudWasOn:[self iCloudOn]];
    
}

// Add right after the refresh method
#pragma mark UIAlertViewDelegate

- (void)alertView:(UIAlertView *)alertView didDismissWithButtonIndex:(NSInteger)buttonIndex {
    
    // @"Automatically store your documents in the cloud to keep them up-to-date across all your devices and the web."
    // Cancel: @"Later"
    // Other: @"Use iCloud"
    if (alertView.tag == 1) {
        if (buttonIndex == alertView.firstOtherButtonIndex) 
        {
            [self setiCloudOn:YES];            
            [self refresh];
        }                
    } 
}

There’s a lot of code here, but none of it is very complicated – it’s just implementing the logic discussed earlier. Be sure to read through the refresh method to make sure you understand the basic logic there.

You may wonder why we’re calling “startQuery” even if iCloud is off. We’re going to need valid a list of iCloud files for when we implement the iCloudToLocal method. But don’t worry, we won’t display the list if iCloud is off. More on this later.

There’s one final step. Since the user can leave the app and change the value in Settings at any time, we need to refresh whenever the user returns to the app (i.e. the app “didbecomeactive”). So make the following changes to listen for that event:

// Add to the bottom of viewDidLoad
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];

// Add right after viewDidLoad
- (void)didBecomeActive:(NSNotification *)notification {    
    [self refresh];
}

Run the app and perform the following steps:

  • Make sure iCloud is off and create a local file. Switch to settings and turn iCloud on. You should no longer see the file.
  • Make sure iCloud is on and go to Settings\iCloud\Documents and Data and switch it off. This effectively makes iCloud not available. Switch back to the app and you should get a popup, and it should switch to iCloud off automatically.
  • Switch back to Settings and make iCloud available again. Switch back to the app and it will tell you iCloud is available and ask if you want to turn it on.
  • Play around with switching iCoud on and off, ana make sure the appropriate “local => iCloud” or “iCloud => local” messages print out.

Nice! Now that we have this firm framework in place, all we really need to care about from here on out is whether “iCloudOn” is YES or NO. If YES, we should be displaying iCloud files, otherwise local files.

But we haven’t covered yet how to get a list of iCloud files. Let’s cover that now!

Contributors

Over 300 content creators. Join our team.