Warning

This how-to is only relevant if your application is already using Core Data. If you need to simultaneously add Core Data and iCloud, you may have to adapt step 4 to fit your needs.

General explanations on how to use Core Data with iCloud are given here : Using Core Data with iCloud Release Notes.

1st step : Enable iCloud on provisioning profiles

First of all, you have to enable iCloud on your App Id on Apple Provisioning Portal. Go to iOS Dev Center > iOS Provisioning Portal > App IDs. Click on « Configure » for the App ID you want to modify, check “Enable for iCloud” and save your changes.

Step 1

Then, always into provisioning portal, go to “Provisioning”, regenerate development and distribution provisioning profiles using the App ID you just changed, download them and add them to XCode.

2nd step : Enable iCloud at application level

When you stil are into provisioning portal, access to (top menu) Member Center > Your Account and carefully note the value of “Individual ID”.

Step 2 - Individual ID

Into XCode, select the root of your project, then “Summary” tab, and check “Enable Entitlements” at the bottom of the page. A new file named <project>.entitlements is created and added to the project's tree. Open this file. Values for keys “com.apple.developer.ubiquity-container-identifiers” and “com.apple.developer.ubiquity-kvstore-identifier” must look like “$(TeamIdentifierPrefix)<project bundle identifier>”. If it's not the case, fix them. If you want, you can replace “$(TeamIdentifierPrefix)” with your individual ID.

Step 2 - Summary tab

Step 2 - Entitlements file

3rd step : Always useful macros

As iCloud is available starting iOS 5.0, it may be useful to have some macros to test against iOS version. You have to add to your project a new header file (i.e. “Macros.h”), and add to it the following content :

#define IOS_VERSION_EQUAL_TO(v)                  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedSame)
#define IOS_VERSION_GREATER_THAN(v)              ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedDescending)
#define IOS_VERSION_GREATER_THAN_OR_EQUAL_TO(v)  ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedAscending)
#define IOS_VERSION_LESS_THAN(v)                 ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] == NSOrderedAscending)
#define IOS_VERSION_LESS_THAN_OR_EQUAL_TO(v)     ([[[UIDevice currentDevice] systemVersion] compare:v options:NSNumericSearch] != NSOrderedDescending)

4th step : Changes to AppDelegate

Now, to have to modify the AppDelegate to manage iCloud.

First of all, check your AppDelete has a method to save Core Data context (with old XCode projects, it's named “- (void)saveContext”). Add calls to this method to the following ones : “- (void)applicationWillResignActive:(UIApplication *)application”, “- (void)applicationDidEnterBackground:(UIApplication *)application”, “- (void)applicationWillEnterForeground:(UIApplication *)application” et “- (void)applicationWillTerminate:(UIApplication *)application”. As iCloud synchronisation will be triggered by Core Data context saves, you don't want to miss one.

Then you have to modify “- (NSManagedObjectContext *)managedObjectContext”:

- (NSManagedObjectContext *)managedObjectContext
{
    if (__managedObjectContext != nil)
    {
        return __managedObjectContext;
    }
 
    NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
 
    if (coordinator != nil)
    {
        if (IOS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"5.0")) {
            NSManagedObjectContext* moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
 
            [moc performBlockAndWait:^{
                [moc setPersistentStoreCoordinator: coordinator];
 
                [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:coordinator];
            }];
            __managedObjectContext = moc;
        } else {
            __managedObjectContext = [[NSManagedObjectContext alloc] init];
            [__managedObjectContext setPersistentStoreCoordinator:coordinator];
        }
 
    }
    return __managedObjectContext;
}

The section for iOS 5 is here to asynchronously manage context changes and iCloud notifications.

The you need to modify “- (NSPersistentStoreCoordinator *)persistentStoreCoordinator”:

- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
    if (__persistentStoreCoordinator != nil)
    {
        return __persistentStoreCoordinator;
    }
 
    NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"<sqlite file>.sqlite"];
 
    __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
 
 
    NSPersistentStoreCoordinator* psc = __persistentStoreCoordinator;
 
    if (IOS_VERSION_GREATER_THAN_OR_EQUAL_TO(@"5.0")) {
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSFileManager *fileManager = [NSFileManager defaultManager];
 
            // Migrate datamodel
            NSDictionary *options = nil;
 
            // this needs to match the entitlements and provisioning profile
            NSURL *cloudURL = [fileManager URLForUbiquityContainerIdentifier:@"<individual ID>.<project bundle identifier>"];
            NSString* coreDataCloudContent = [[cloudURL path] stringByAppendingPathComponent:@"data"];
            if ([coreDataCloudContent length] != 0) {
                // iCloud is available
                cloudURL = [NSURL fileURLWithPath:coreDataCloudContent];
 
                options = [NSDictionary dictionaryWithObjectsAndKeys:
                           [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                           [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                           @"<app name>.store", NSPersistentStoreUbiquitousContentNameKey,
                           cloudURL, NSPersistentStoreUbiquitousContentURLKey,
                           nil];
            } else {
                // iCloud is not available
                options = [NSDictionary dictionaryWithObjectsAndKeys:
                           [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                           [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                           nil];
            }
 
            NSError *error = nil;
            [psc lock];
            if (![psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
            {
                NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
                abort();
            }
            [psc unlock];
 
            dispatch_async(dispatch_get_main_queue(), ^{
                NSLog(@"asynchronously added persistent store!");
                [[NSNotificationCenter defaultCenter] postNotificationName:@"RefetchAllDatabaseData" object:self userInfo:nil];
            });
 
        });
 
    } else {
        NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:
                   [NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
                   [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
                   nil];
 
        NSError *error = nil;
        if (![__persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error])
        {
            NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
            abort();
        }
    }
    return __persistentStoreCoordinator;
}

Finally, you have to add method managing changes from iCloud:

- (void)mergeiCloudChanges:(NSNotification*)note forContext:(NSManagedObjectContext*)moc {
    [moc mergeChangesFromContextDidSaveNotification:note]; 
 
    NSNotification* refreshNotification = [NSNotification notificationWithName:@"RefreshAllViews" object:self  userInfo:[note userInfo]];
 
    [[NSNotificationCenter defaultCenter] postNotification:refreshNotification];
}
 
// NSNotifications are posted synchronously on the caller's thread
// make sure to vector this back to the thread we want, in this case
// the main thread for our views & controller
- (void)mergeChangesFrom_iCloud:(NSNotification *)notification {
	NSManagedObjectContext* moc = [self managedObjectContext];
 
    // this only works if you used NSMainQueueConcurrencyType
    // otherwise use a dispatch_async back to the main thread yourself
    [moc performBlock:^{
        [self mergeiCloudChanges:notification forContext:moc];
    }];
}

5th step : Changes to the controllers using Core Data context.

As iCloud changes are asynchronously received, you have to manage them with the help of the notifications created into the AppDelegate. It may be done into RootViewController (adapt to fit your needs).

First, update the “RootViewController.h” file to add the following property

@property (nonatomic, retain) NSFetchedResultsController *fetchedResultsController;

and the following private block :

@private  
    // because ivars should be private, and it is really important
    // that all code always goes through the accessor methods to ensure that these
    // are properly initialized.  Without the funny __ then KVC might "help" us too much
    // With iCloud importing data asynchronously, there are more timing and multi-threading issues
    NSFetchedResultsController *fetchedResultsController__ ;
    NSManagedObjectContext *managedObjectContext__;

Then, update “RootViewController.m”

Add the following synthesize statements

@synthesize fetchedResultsController=__fetchedResultsController;
@synthesize managedObjectContext=__managedObjectContext;

and add the following getter.

- (NSFetchedResultsController *)fetchedResultsController
{
    if (__fetchedResultsController != nil)
    {
        return __fetchedResultsController;
    }
 
    /*
     Set up the fetched results controller.
    */
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"<entity name>" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];
 
    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];
 
    // Edit the sort key as appropriate.
    NSSortDescriptor *sortDescriptorName = [[NSSortDescriptor alloc] initWithKey:@"<sort key>" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptorName, nil];
 
    [fetchRequest setSortDescriptors:sortDescriptors];
 
    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"<section name key path>" cacheName:@"<cache name>"];
    aFetchedResultsController.delegate = self;
 
    self.fetchedResultsController = aFetchedResultsController;
 
    [aFetchedResultsController release];
    [fetchRequest release];
    [sortDescriptorName release];
    [sortDescriptors release];
 
	NSError *error = nil;
	if (![self.fetchedResultsController performFetch:&error])
        {
	    NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
	    abort();
	}
 
    return __fetchedResultsController;
}

Then, add the following method.

// because the app delegate now loads the NSPersistentStore into the NSPersistentStoreCoordinator asynchronously
// we will see the NSManagedObjectContext set up before any persistent stores are registered
// we will need to fetch again after the persistent store is loaded
- (void)reloadFetchedResults:(NSNotification*)note {
    NSError *error = nil;
	if (![[self fetchedResultsController] performFetch:&error]) {
		/*
		 Replace this implementation with code to handle the error appropriately.
 
		 abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. If it is not possible to recover from the error, display an alert panel that instructs the user to quit the application by pressing the Home button.
		 */
		NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
		abort();
	}		
 
    if (note) {
        [self.tableView reloadData];
    }
}

You have to modify “- (void)viewDidLoad” in order to add a notification observer (raised by the AppDelegate) which run the above method:

- (void)viewDidLoad
{
    [super viewDidLoad];
 
    ... your code...
 
    // observe the app delegate telling us when it's finished asynchronously setting up the persistent store
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadFetchedResults:) name:@"RefetchAllDatabaseData" object:[[UIApplication sharedApplication] delegate]];
}

Finally, you have to remove the observer when unloading the view.

- (void)viewDidUnload
{
    [super viewDidUnload];
 
    // Relinquish ownership of anything that can be recreated in viewDidLoad or on demand.
    // For example: self.myOutlet = nil;
 
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}