I have a simple application which is a UITableView
that gets populated by the user filling in some UITextFields
in a different view. This information gets saved to CoreData
and the UITableView
gets updated using NSFetchedResultsController
.
I'm now introducing iCloud synching into my environment. With reference to a previously asked question: Existing Core Data Database Not Uploading to iCloud When Enabling iCloud On My App , I am still having issues but it was too messy to update that question with my new code. I am referring specifically to the WWDC 2013 CoreData
Sessions, as well as this. Because my app is iOS 7 only. I'm fairly new to iCloud development and am an intermediate programmer.
I am having some great difficulty migrating my existing data to iCloud when I update from the App Store (non-iCloud) version to the development (iCloud) version. New data syncs across perfectly.
I understand that I need to essentially take my existing store without iCloud and migrate it across to iCloud but it's just not working out for me.
Here's some code to illustrate what I'm working on:
Update
I have gone through and thought about this and have removed code that was redundant.
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Planner.sqlite"];
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *url = [fm URLForUbiquityContainerIdentifier:nil];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (url)
{
NSLog(@"iCloud is enabled");
NSDictionary *options = @{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES,
};
NSPersistentStore *localStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error];
[_persistentStoreCoordinator migratePersistentStore:localStore toURL:[self iCloudURL] options:[self iCloudStoreOptions] withType:NSSQLiteStoreType error:&error];
}
else
{
NSLog(@"iCloud is not enabled");
NSDictionary *options = @{
NSMigratePersistentStoresAutomaticallyOption : @YES,
NSInferMappingModelAutomaticallyOption : @YES
};
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:[self iCloudURL] options:options error:&error])
{
NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
abort();
}
}
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:@"Reload" object:self userInfo:nil];
});
return _persistentStoreCoordinator;
}
I am no longer calling the migrateLocalStoreToiCloud method but it is here for reference:
/*-(void)migrateLocalStoreToiCloud
{
//assuming you only have one store.
NSPersistentStore *store = [[_persistentStoreCoordinator persistentStores] firstObject];
NSPersistentStore *newStore = [_persistentStoreCoordinator migratePersistentStore:store
toURL:[self iCloudURL]
options:[self iCloudStoreOptions]
withType:NSSQLiteStoreType
error:nil];
[self reloadStore:newStore];
}
*/
The storeURL
, storeOptions
, iCloudURL
and iCloudStoreOptions
code is:
- (NSURL *)iCloudURL
{
NSFileManager *fm = [NSFileManager defaultManager];
NSURL *url = [fm URLForUbiquityContainerIdentifier:nil];
if (url) {
NSLog(@"iCloud access at %@", url);
} else {
NSLog(@"No iCloud access");
}
return url;
}
-(NSURL *)storeURL{
return [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Planner.sqlite"];
}
-(NSDictionary *)storeOptions{
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
return options;
}
-(NSDictionary *)iCloudStoreOptions{
NSMutableDictionary *options = [[NSMutableDictionary alloc] init];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSMigratePersistentStoresAutomaticallyOption];
[options setObject:[NSNumber numberWithBool:YES] forKey:NSInferMappingModelAutomaticallyOption];
[options setObject:@"PlannerStore" forKey:NSPersistentStoreUbiquitousContentNameKey];
return options;
}
Problem
When I run this code exactly as it is, I am not presented in the console with Using Local Storage : 1
and 0
at all, though the data is there.
If I change the persistentStoreCoordinator
where it first adds the persistentStore to use [self iCloudStoreOptions]
as the options, the Using Local Storage 1
and 0
displays, but the data does not on the device.
I'm following the guidance here and I have tried multiple variations of my code including not removing any persistentStore in the reloadStore method but I am completely stuck.
I have read so many posts but it seems that I just cannot find someone who is migrating data from a non-iCloud version to an iCloud version and with examples I have found, there's no code.
I have looked into this and while it is a great code set, I've downloaded the code and followed those guidelines where it doesn't remove any persistentStore but it still wouldn't work for me. I can't imagine what I'm trying to achieve is very complicated. I simply need to take the existing non-iCloud data and migrate it over to iCloud.
Update
With reading Apple's guide : https://developer.apple.com/LIBRARY/ios/documentation/DataManagement/Conceptual/UsingCoreDataWithiCloudPG/UsingSQLiteStoragewithiCloud/UsingSQLiteStoragewithiCloud.html#//apple_ref/doc/uid/TP40013491-CH3-SW2 I can see that my migrateLocaltoiCloud
method is currently wrong with the removing of the persistentStore
, etc, but I can't figure out how to fix it. If I call this from the persistentStoreCoordinator
, will it somehow interfere with the DidChangeNotification
?
I'm really stuck on this and I would appreciate any guidance into the right direction
I am providing an answer here to my question because it's clearer than updating the question and after a lot of debugging, I have this working, to some extent.
With debugging the addPersistentStore
and migratePersistentStore
lines of code, I came up with:
NSPersistentStore *localStore = [_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&error];
NSLog(@"The localStore is %@", localStore);
NSPersistentStore *migratedStore = [_persistentStoreCoordinator migratePersistentStore:localStore toURL:[self iCloudURL] options:[self iCloudStoreOptions] withType:NSSQLiteStoreType error:&error];
NSLog(@"The migrationStore is %@", migratedStore);
The NSLog value of localStore is: The localStore is <NSSQLCore: 0x13451b100> (URL: file:///var/mobile/Applications/DDB71522-C61D-41FC-97C7-ED915D6C46A4/Documents/Planner.sqlite)
The NSLog value of migratedStore is: (null)
With debugging, I could see essentially the same thing, that migrateStore was always null.
I changed the code slightly:
NSPersistentStore *migratedStore = [_persistentStoreCoordinator migratePersistentStore:localStore toURL:storeURL options:[self iCloudStoreOptions] withType:NSSQLiteStoreType error:&error];
For the URL to be the storeURL and suddenly, everything started working, where storeURL:
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"Planner.sqlite"];
My outputs were:
The localStore is <NSSQLCore: 0x14ee09b50> (URL: file:///var/mobile/Applications/CC0F7DA0-251F-46D6-8E43-39D63062AED2/Documents/Planner.sqlite)
[PFUbiquitySwitchboardEntryMetadata setUseLocalStorage:](771): CoreData: Ubiquity: mobile~5BD59259-7758-42FB-986F-DDD173F75ED3:EnStor
The migrationStore is <NSSQLCore: 0x157e260b0> (URL: file:///var/mobile/Applications/A08D183D-ECD4-4E72-BA64-D21727DE7A3E/Documents/CoreDataUbiquitySupport/mobile~CB6C3699-8BF1-47DB-9786-14BCE1CD570A/EnStor/771D2B0B-870A-428E-A9A8-5DAE1295AF53/store/Planner.sqlite)
The Using Local Store went from 1 to 0 and my data from the App Store version of the app continued to be there through the migration to the new version. This was the output the first time I ran the app after the upgrade.
This is great news.
Every subsequent time I ran the app though, the data continued to migrate and so I would end up with duplicate results.
The issue also is the fact that when adding a new device, it doesn't pull down the information to the new device.
I don't quite understand why using storeURL as the url for migratePersistentStore worked?
I also don't want each new device to basically add a new persistent store if one already exists and I don't know how I would get around this and also I don't want the migration to occur again and again. It should only occur the one time.
I'm getting there, but still a lot of work to be done!