Introduction
The aim of this tutorial is to show how to add iCloud support to a project. The starting point will be the final project from the end of the Core Data Universal / iPad Storyboard tutorial. I would recommend going through that tutorial first so you’re familiar with the Staff Manager project.
Prerequisites
Download the final project from the end of the Core Data Universal / iPad Storyboard tutorial. Extract and open it with Xcode.
Note: iCloud does NOT work in the iOS Simulator. I recommend testing on two devices, such as an iPad and an iPhone at the same time.
Create iCloud Staff Manager App ID
Head over to https://developer.apple.com/membercenter/ then enter the iOS Provisioning Portal. The link looks like this:
Select App IDs
Select New App ID
Configure the App ID as follows then click Submit:
Click Configure:
Tick Enable for iCloud then click Done:
iCloud is now enabled for Staff Manager:
You will notice that the App ID has a prefix of the Team ID. You will have a different Team ID prefix to me. You will need to substitute your own prefix whenever you see my prefix in code:
![]()
To use the new App ID you’ll need a new Provisioning Profile so click Provisioning then New Profile:
Configure the Provisioning Profile as follows:
Click Submit then download your new profile. If you can’t see the Download button refresh the page:
Once you have downloaded the provisioning profile file double click it which will automatically import it into Xcode:
In Xcode, configure the Staff Manager Code Signing Identity to use the new Provisioning Profile:
Enable Staff Manager Entitlements
To use the iCloud App ID we’ve just created we need to enable some Entitlements. Select the Staff Manager Targets:
Scroll to the Entitlements section (down the bottom) then configure it as follows:
Configure Core Data for iCloud
Currently the Persistent Store (StaffManager.sqlite) is stored on the local device. Once configured for iCloud it will still reside locally yet in a special iCloud enabled directory.
After poring through many approaches for enabling iCloud + Core Data I’ve come up with some succinct code used to create the persistent store. For re-usability and clarity the following variables are used:
- iCloudEnabledAppID is the full App ID (including the Team Prefix). You will need to change this to match the Team Prefix found in your own iOS Provisioning Portal.
- dataFile is the name of the SQLite database store file.
- iCloud is the URL to your apps iCloud root path.
- dataDirectory is the name of the directory the database will be stored in. It should always end with .nosync
- logsDirectory is the name of the directory the database change logs will be stored in.
- iCloudData = iCloud + dataDirectory
- iCloudLogs = iCloud + logsDirectory
The upcoming code is responsible for returning a NSPersistentStoreCoordinator. Most of it is for setting what/where data and logs will be stored. The options NSMutableDictionary is used to set some new important options:
- NSPersistentStoreUbiquitousContentNameKey will become the iCloud root (based on App ID)
- NSPersistentStoreUbiquitousContentURLKey will become the change logs area.
Delete everything within the persistentStoreCoordinator method of AppDelegate.m then paste in the following code:
if((__persistentStoreCoordinator != nil)) { return __persistentStoreCoordinator; } __persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]]; NSPersistentStoreCoordinator *psc = __persistentStoreCoordinator; // Set up iCloud in another thread: dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // ** Note: if you adapt this code for your own use, you MUST change this variable: NSString *iCloudEnabledAppID = @"5KFJ75859U.Tim-Roadley.Staff-Manager"; // ** Note: if you adapt this code for your own use, you should change this variable: NSString *dataFileName = @"StaffManager.sqlite"; // ** Note: For basic usage you shouldn't need to change anything else NSString *iCloudDataDirectoryName = @"Data.nosync"; NSString *iCloudLogsDirectoryName = @"Logs"; NSFileManager *fileManager = [NSFileManager defaultManager]; NSURL *localStore = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:dataFileName]; NSURL *iCloud = [fileManager URLForUbiquityContainerIdentifier:nil]; if (iCloud) { NSLog(@"iCloud is working"); NSURL *iCloudLogsPath = [NSURL fileURLWithPath:[[iCloud path] stringByAppendingPathComponent:iCloudLogsDirectoryName]]; NSLog(@"iCloudEnabledAppID = %@",iCloudEnabledAppID); NSLog(@"dataFileName = %@", dataFileName); NSLog(@"iCloudDataDirectoryName = %@", iCloudDataDirectoryName); NSLog(@"iCloudLogsDirectoryName = %@", iCloudLogsDirectoryName); NSLog(@"iCloud = %@", iCloud); NSLog(@"iCloudLogsPath = %@", iCloudLogsPath); if([fileManager fileExistsAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName]] == NO) { NSError *fileSystemError; [fileManager createDirectoryAtPath:[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName] withIntermediateDirectories:YES attributes:nil error:&fileSystemError]; if(fileSystemError != nil) { NSLog(@"Error creating database directory %@", fileSystemError); } } NSString *iCloudData = [[[iCloud path] stringByAppendingPathComponent:iCloudDataDirectoryName] stringByAppendingPathComponent:dataFileName]; NSLog(@"iCloudData = %@", iCloudData); NSMutableDictionary *options = [NSMutableDictionary dictionary]; [options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption]; [options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption]; [options setObject:iCloudEnabledAppID forKey:NSPersistentStoreUbiquitousContentNameKey]; [options setObject:iCloudLogsPath forKey:NSPersistentStoreUbiquitousContentURLKey]; [psc lock]; [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[NSURL fileURLWithPath:iCloudData] options:options error:nil]; [psc unlock]; } else { NSLog(@"iCloud is NOT working - using a local store"); NSMutableDictionary *options = [NSMutableDictionary dictionary]; [options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption]; [options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption]; [psc lock]; [psc addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:localStore options:options error:nil]; [psc unlock]; } dispatch_async(dispatch_get_main_queue(), ^{ [[NSNotificationCenter defaultCenter] postNotificationName:@"SomethingChanged" object:self userInfo:nil]; }); }); return __persistentStoreCoordinator; |
Comment out the following code found in the didFinishLaunchingWithOptions method of AppDelegate.m :
/*
[self setupFetchedResultsController];
if (![[self.fetchedResultsController fetchedObjects] count] > 0 ) {
NSLog(@"!!!!! ~~> There's nothing in the database so defaults will be inserted");
[self importCoreDataDefaultRoles];
}
else {
NSLog(@"There's stuff in the database so skipping the import of default data");
}
*/ |
Configure Notifications
When underlying data changes unfortunately the table views have no idea. To fix this we need to get them to watch for stuff changing notifications. First we need to configure the managedObjectContext so replace the managedObjectContext method in AppDelegate.m with the following two methods:
- (NSManagedObjectContext *)managedObjectContext { if (__managedObjectContext != nil) { return __managedObjectContext; } NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator]; if (coordinator != nil) { 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; } return __managedObjectContext; } - (void)mergeChangesFrom_iCloud:(NSNotification *)notification { NSLog(@"Merging in changes from iCloud..."); NSManagedObjectContext* moc = [self managedObjectContext]; [moc performBlock:^{ [moc mergeChangesFromContextDidSaveNotification:notification]; NSNotification* refreshNotification = [NSNotification notificationWithName:@"SomethingChanged" object:self userInfo:[notification userInfo]]; [[NSNotificationCenter defaultCenter] postNotification:refreshNotification]; }]; } |
That code tells the Managed Object Context to post notifications when iCloud updates data. To use those notifications add the following method to RolesTVC.m and PersonsTVC.m:
- (void)reloadFetchedResults:(NSNotification*)note { NSLog(@"Underlying data changed ... refreshing!"); [self performFetch]; } |
Also add the following to the end of the viewDidLoad method of RolesTVC.m and PersonsTVC.m:
// Refresh this view whenever data changes [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadFetchedResults:) name:@"SomethingChanged" object:[[UIApplication sharedApplication] delegate]]; |
Finally, add the following method to RolesTVC.m and PersonsTVC.m which cleans up a little:
- (void)viewDidUnload { [[NSNotificationCenter defaultCenter] removeObserver:self]; } |
That’s it!
Run the app on the iPad and iPhone at the same time. You should see changes made on one device reflected on the other pretty quickly.
There are a couple of things I’m not entirely happy with just yet and will work to resolve soon. These are:
- Saving every keystroke for the purpose of updating the master split view isn’t fast.
- Importing default data.
Hopefully you have enough information to get you started with iCloud anyway.
Here’s the complete source code so far
If you liked this tutorial or found something wrong with it please let me know!
If you want to support my work and have an iPad please consider purchasing iSoccer or making a small donation. I’m saving for a Retina Tutorial iPad!!
-Tim
Go to the Tutorials Index








































































































