16

How can I enable iCloud Core Data in an app which already uses local storage Core Data?

I've tried to use NSPersistentStoreUbiquitousContentNameKey in my persistent store options. Unfortunately, this option enables iCloud but does not transfer any of the local data to iCloud. I can't seem to get migratePersistentStore:toURL:options:withType:error: to work either. I provide the persistent store, its URL, iCloud options, etc. and it still will not migrate the existing local data to iCloud. Here's how I'm using the method:

- (void)migratePersistentStoreWithOptions:(NSDictionary *)options {
    NSError *error;
    self.storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:[NSString stringWithFormat:@"%@.sqlite", self.SQLiteFileName]];

    NSPersistentStore *store = [self.persistentStoreCoordinator migratePersistentStore:self.persistentStoreCoordinator.persistentStores.firstObject toURL:self.storeURL options:options withType:NSSQLiteStoreType error:&error];
    if (store) NSLog(@"[CoreData Manager] Store was successfully migrated");
    else NSLog(@"[CoreData Manager] Error migrating persistent store: %@", error);
} 

The local storage remains separate from the iCloud storage. If possible, I'd like to move the local Core Data to iCloud without manually transferring each entity.

Any ideas? I can find lots of articles, tutorials, and posts about moving back to local storage from iCloud - but I want to move from local storage to iCloud.

Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
  • *"I can't seem to get migratePersistentStore:toURL:options:withType:error: to work either."* In what way, specifically, is it not working? – Tom Harrington Aug 17 '14 at 18:43
  • @TomHarrington See my most recent edit. When I call the method and specify the store and its URL, nothing gets moved - the local storage stays put and the iCloud storage remains empty. – Sam Spencer Aug 17 '14 at 22:21

2 Answers2

22

Here's what you'll need to do

  1. Create a local NSPersistentStoreCoordinator
  2. Add your existing persistent store to that coordinator and store a reference to this new returned store.
  3. Call that handy migratePersistStore:... providing the store from #2, a URL for the store in the documents directory with a different file name and the all important options including the NSPersistentStoreUbiquitousContentNameKey key.

Here's the code, notes in-line.

NSURL *documentsDirectory = [[[NSFileManager defaultManager] URLsForDirectory:NSDocumentDirectory inDomains:NSUserDomainMask] lastObject];

//This is the path to the new store. Note it has a different file name
NSURL *storeURL = [documentsDirectory URLByAppendingPathComponent:@"TestRemote.sqlite"];

//This is the path to the existing store
NSURL *seedStoreURL = [documentsDirectory URLByAppendingPathComponent:@"Test.sqlite"];

//You should create a new store here instead of using the one you presumably already have access to
NSPersistentStoreCoordinator *coord = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:self.managedObjectModel];

NSError *seedStoreError;
NSDictionary *seedStoreOptions = @{ NSReadOnlyPersistentStoreOption: @YES };
NSPersistentStore *seedStore = [coord addPersistentStoreWithType:NSSQLiteStoreType
                                                   configuration:nil
                                                             URL:seedStoreURL
                                                         options:seedStoreOptions
                                                           error:&seedStoreError];

NSDictionary *iCloudOptions = @{ NSPersistentStoreUbiquitousContentNameKey: @"MyiCloudStore" };
NSOperationQueue *queue = [[NSOperationQueue alloc] init];

//This is using an operation queue because this happens synchronously
[queue addOperationWithBlock:^{
    NSError *blockError;
    [coord migratePersistentStore:seedStore
                            toURL:storeURL
                          options:iCloudOptions
                         withType:NSSQLiteStoreType
                            error:&blockError];

    NSOperationQueue *mainQueue = [NSOperationQueue mainQueue];
    [mainQueue addOperationWithBlock:^{
        // This will be called when the migration is done
    }];
}];

Note that after you do this migration, you'll need to configure the persistent store you use with your MOC with the new URL and always include the iCloudOptions above with the NSPersistentStoreUbiquitousContentNameKey key.

This was based on Apple's documentation.

After completion, you should see a new folder in your Documents folder in the simulator folder (~/Library/Application Support/iPhone Simulator/...) labeled CoreDataUbiquitySupport. Nested deep in there is your iCloud synced sqlite store.

Tada!


EDIT: Oh and make sure you have created an iCloud entitlement and included it in your bundle. You should be able to do that all within Xcode, but you can update it on the development portal too.

Sam Spencer
  • 8,492
  • 12
  • 76
  • 133
Acey
  • 8,048
  • 4
  • 30
  • 46
  • But for device I unable to see this to be working.How to should I check the file is moved to iCloud or not for a device? – The iCoder Nov 06 '14 at 14:40
  • 1
    You should be able to run the app on another device logged into the same iCloud account and see that the file is copied over by trying to open the file with the same URL, after iCloud has synced. If you want to see the actual file in the iPhone's file system, you will need to jailbreak your device, ssh into it and look in your app's folder. A little overkill vs testing for the results I think. – Acey Nov 06 '14 at 16:57
  • @Acey this is a great answer. But how do I prevent the app from running this every time my app gets started? Is there a way to run this only once, or shall I just check for a bool flag and set it when the migration is done the first time: `[[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"migratedToiCloud"];` – Houman May 25 '15 at 11:42
  • Well, I suppose after the migration has completed you could remove the old database file. Check if that is there on launch to determine if you need to migrate. – Acey May 25 '15 at 18:47
  • what if I want to do schema migration as well? Wouldn't Core Data complain about the momd being different when adding the seed store? Or can it do lightweight migration as well? – JonEasy Aug 01 '15 at 06:15
  • That's a great topic for a new question! – Acey Aug 01 '15 at 12:13
  • That is a reference to an `NSManagedObjectModel`, typically created with its initializer (`initWithContentsOfURL:`) that loads a model from a .momd file. It's just part of the boilerplate included with a sample CoreData project so I omitted it. – Acey Oct 17 '16 at 06:16
  • would below function do it??? - (NSManagedObjectModel *) getAManagedObjectModel { NSURL *modelURL = [[NSBundle mainBundle] URLForResource:@"AppName" withExtension:@"momd"]; return [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL]; } – coolcool1994 Oct 17 '16 at 13:44
  • Questions: when many devices (iPhones, iPads) using the same app make this @"TestRemote.sqlite" iCloud syncing store and migrate local store to it, would each device's migration overriding the previous save to iCloud? And if you want to sync to the iCloud store in one of the devices, would you simply have to migrate from the local @"TestRemote.sqlite"-iCloud-syncing-store to the device's local store using the same way as above? If so, how do you make sure the local @"TestRemote.sqlite"-iCloud-syncing-store has been updated to the latest one. – coolcool1994 Oct 17 '16 at 14:00
  • This isn't a good place to have discussions. If you have further questions about multiple devices using the same iCloud Core Data Storage, please ask a new question. As for your previous question, the code is fine but I would NOT make a getter to do that. Just set an iVar or local var with the model when setting up the core data stack. Again, it's part of the core data boilerplate in the sample app, I suggest you reference that. – Acey Oct 17 '16 at 15:53
2

Take a look at this sample app which includes code to migrate a local core data store to iCloud and back again. Best read the associated docs and build the sample apps in your environment to get them working and once they are working then try and refactor your code to use a similar approach.

Feel free to send me an email for further help. Apologies for not giving you an answer here but it can be quite a complicated issue to deal with.

http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/

Duncan Groenewald
  • 8,496
  • 6
  • 41
  • 76