iCloud: Sync Core Data between IOS and OSX - ios

I try to sync core data between IOS and OSX. At both apps I have the same configuration:
And the same entitlements:
I also use the same code for the store coordinator within the same name for sqlite file and url:
NSManagedObjectModel* managedModel = [NSManagedObjectModel mergedModelFromBundles:nil];
NSPersistentStoreCoordinator* storeCooordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:managedModel];
//-> start iCloud
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSURL* applicationFilesDirectory = [JHDCoreDataDAO applicationFilesDirectory];
NSURL* storeURL = [applicationFilesDirectory URLByAppendingPathComponent:DATABASE_NAME];
if(!storeURL) { NSLog(#"Error reading applicationFilesDirectory for given sqlite resouce"); }
NSString* containerIdentifier = [NSString stringWithFormat:#"%#.%#",TEAM_IDENTIFIER,APP_IDENTIFIER];
NSURL* iCloud = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:containerIdentifier];
NSString *iCloudEnabledAppID = #"TEAMID~com~sample~sample";
NSError* persistentStoreError;
if (iCloud) {
NSLog(#"iCloud is working");
NSLog(#"iCloudEnabledAppID = %#",iCloudEnabledAppID);
NSLog(#"iCloud URL: %#",iCloud);
NSString* cloudPath = [[iCloud path] stringByAppendingPathComponent:#"data"];
NSURL* transactionsLogUrl = [NSURL fileURLWithPath:cloudPath];
NSDictionary* options = [NSDictionary dictionaryWithObjectsAndKeys:
iCloudEnabledAppID, NSPersistentStoreUbiquitousContentNameKey,
transactionsLogUrl, NSPersistentStoreUbiquitousContentURLKey,
[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption,
[NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption,
nil];
[storeCooordinator lock];
if(![storeCooordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&persistentStoreError])
{
NSLog(#"Fehler: %#, %#", persistentStoreError.localizedDescription, persistentStoreError.userInfo);
abort();
}
[storeCooordinator unlock];
} else {
//Handle local psc
}
});
Each app, the IOS Version and the OSX Version, are still running perfectly within the iCloud. Each app handle its own database, due to there is no sync between this two apps. Is there somethings what I have forgotton?

You need to be very careful that the identifiers you use in the entitlements match what you use in the source code to access the ubiquity container.
One complication when syncing multiple apps (e.g. Mac app and iOS app) is that you will need to check that they each have the same team identifier. If not, you will want to fill in the team id explicitly for iCloud entitlements, rather than using the TeamIdentifierPrefix variable. Pick one of the team ids and use that for both apps.
In your particular example, it looks like you have entitlements setup for a container id that ends in sample.sample.sample. Assuming the team id is the same for each app, your container id should be XXXXXXXXXX.sample.sample.sample, where the first part is your team id. I don't know what APP_IDENTIFIER is in this instance, but it should be checked to make sure it is sample.sample.sample. (My guess is that it isn't.)
The NSPersistentStoreUbiquitousContentNameKey setting can be basically any label you like for your store. It doesn't have to use the ~ or even be reverse-DNS form. For example, you could call it 'Store1'.
I find the easiest way to see if everything is setup OK is to go to the ~/Library/Mobile Documents folder on your Mac, and look for the app container. You can just browse the contents directly in Finder.
Unfortunately, this is probably the easiest part of getting Core Data working with iCloud. There will be many more hurdles, and they tend to be more challenging. There are new solutions coming up for syncing Core Data all the time, including the TICDS framework and Core Data Ensembles, both of which work with iCloud. (Disclosure: I have contributed to both projects, and founded the Ensembles project.)

Related

Prepopulated sqlite file in the app bundle. What happens after the existing users update?

I have an app on the store which points to the appname.sqlite file in the document directory.Here's the old code:
NSURL *storeURL = [[self applicationDocumentsDirectory]URLByAppendingPathComponent:#"MyApp.sqlite"];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:[self managedObjectModel]];
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:#{NSMigratePersistentStoresAutomaticallyOption : #YES,NSInferMappingModelAutomaticallyOption : #YES}; error:&error]) {
abort();
}
I now want to give an update. The new feature being a prefilled .sqlite database which is there in the bundle.See the following
NSURL *storeURL = [NSURL fileURLWithPath: [[NSBundle mainBundle] pathForResource:#"MyNewPrefilled" ofType:#"sqlite"]];
It would work ok on the fresh installation the app.But what happens to the users who have logged in and have a lot of data saved in the db (old sqlite in the app directory)? I would lose it because I now am pointing to the .sqlite in the app bundle (MyNewPrefilled.sqlite) and not the app directory one. How do I get the old data back from the old sqlite file of doc directory to my new one in the bundle?
FYI: I use code data migration already (involving version numbers in datamodels) which works ok when i change datamodel in updates.
Apple recommends to use two persistent stores (in the same model) to separate data that is user generated and data that is static. Presumably, if your data is static, you should do exactly that and have a read-only store for the static data. See for example Configurations in the Core Data Programming Guide.
Another approach is
to copy the bundle seed database into the documents directory, and then
read the data from the old store and
copy it to the new one.
Finally, you can delete the old store.

Move local Core Data to iCloud

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.
Here's what you'll need to do
Create a local NSPersistentStoreCoordinator
Add your existing persistent store to that coordinator and store a reference to this new returned store.
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.
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/

Updating Core Data +iCloud model with the next App version

I have to work on the next version of an Application that uses Core Data and iCloud.
iCloud has been activated in the current version using the most "modern" way:
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:#"data_cfg"
URL:storeURL
options:#{NSPersistentStoreUbiquitousContentNameKey:#"theStore"}
error:&error])
Now I need to update the store structure adding new entities, relationship and so on...
I'm totally stuck because I don't know which is the better way to update a model knowing that iCloud is synchronised with users data.
Which is the best way to perform an update like that? What should I be aware of and what I should pay attention for the most? How to migrate the current data?
Updating a model when using iCloud is the same as when not using iCloud-- with the sole exception that your changes must be ones that work with automatic lightweight migration. Apple's documentation describes the requirements for this kind of migration in detail. The basic steps are:
Create a new version of the data model, and make this version "current". (You must keep the old model around, so now you'll have two, but only one is current).
Make your model changes in the new version.
Add NSMigratePersistentStoresAutomaticallyOption and NSInferMappingModelAutomaticallyOption to your options dictionary in the code above, using #YES as the value for both.
Now when you launch the app, Core Data will compare the old and new versions of the model and (assuming your changes work for automatic lightweight migration) modify the persistent store to use the new version.
Keep in mind that iCloud syncing only works between devices that use the same version of the data model. If a user upgrades your app on one device but not another that they use, syncing will stop until they upgrade the app on other device as well.
iCloud Backup doesn't matter, by the way here is solution for installing new sqlite model.
If the data inside the Application can be recreated/downloaded from server, there is a great solution.
You don't need to setup migration stack, there is a very quick solution. The trick is to delete the old sqlite database and create a new one.
Here is the code that I used on my application update.
You need to add this in your AppDelegate.m
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator
{
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"YourDatabase.sqlite"];
NSManagedObjectModel *managedObjectModel = [self managedObjectModel];
NSError *error = nil;
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: managedObjectModel];
// Check if we already have a persistent store
if ( [[NSFileManager defaultManager] fileExistsAtPath: [storeURL path]] ) {
NSDictionary *existingPersistentStoreMetadata = [NSPersistentStoreCoordinator metadataForPersistentStoreOfType: NSSQLiteStoreType URL: storeURL error: &error];
if ( !existingPersistentStoreMetadata ) {
// Something *really* bad has happened to the persistent store
//[NSException raise: NSInternalInconsistencyException format: #"Failed to read metadata for persistent store %#: %#", storeURL, error];
NSLog(#"Failed to read metadata for persistent store %#: %#", storeURL, error);
}
if ( ![managedObjectModel isConfiguration: nil compatibleWithStoreMetadata: existingPersistentStoreMetadata] ) {
if ( ![[NSFileManager defaultManager] removeItemAtURL: storeURL error: &error] )
NSLog(#"*** Could not delete persistent store, %#", error);
} // else the existing persistent store is compatible with the current model - nice!
} // else no database file yet
[_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:nil
error:&error];
return _persistentStoreCoordinator;
}
This code covers issues
if existing database is old one, than deleting and setting up new one.
if there is no database yet (user download only the newest version) than creating new database.
if existing database is compatible with new one, just using that.
Just change the #"YourDatabase.sqlite" to your sqliteDB filename and it will work fine.
If your'e data can't be recreated (synced from server), than You need to setup migration stack
here is link to Apple's guide https://developer.apple.com/library/mac/Documentation/Cocoa/Conceptual/CoreDataVersioning/CoreDataVersioning.pdf

App does not store any content in document directory but Appstore reject

I am new to iOS development. My app got rejected from the review, stating the following reason,
2.23 Apps must follow the iOS Data Storage Guidelines or they will be rejected
We found that your app does not follow the iOS Data Storage Guidelines, which is required per the App Store Review Guidelines.
I am not storing my DB file in documents directory. Here's my code,
NSString *libraryPath = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) lastObject];
NSString *path = [libraryPath stringByAppendingPathComponent:#"DatabaseFolder"];
NSURL *pathURL = [NSURL fileURLWithPath:path];
BOOL isDirectory = NO;
if ([[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDirectory]) {
if (isDirectory) {
return pathURL;
} else {
// Handle error. ".data" is a file which should not be there...
[NSException raise:#"'Private Documents' exists, and is a file" format:#"Path: %#", path];
}
}
NSError *error = nil;
if (![[NSFileManager defaultManager] createDirectoryAtPath:path withIntermediateDirectories:YES attributes:nil error:&error]) {
[NSException raise:#"Failed creating directory" format:#"[%#], %#", path, error];
}
return pathURL;
How to reproduce a crash or bug that only App Review or users are seeing?
The iOS Data Storage guideline document (login required to view) says,
Everything in your app’s home directory is backed up, with the exception of the application bundle itself, the caches directory, and temp directory.
This means even your NSLibraryDirectory directory contents gets backed up to iCloud. To resolve this you have following options,
Use the /tmp directory for storage
Use the /Caches directory for storage
Only use the /Documents directory for user-generated content that cannot be re-created.
Set the do not backup attribute on the file using setResourceValue:forKey:error: method of NSURL.
Here is how you can mark a resource for not backing up to iCloud.
- (BOOL)addSkipBackupAttributeToItemAtURL:(NSURL *)URL
{
assert([[NSFileManager defaultManager] fileExistsAtPath: [URL path]]);
NSError *error = nil;
BOOL success = [URL setResourceValue: [NSNumber numberWithBool: YES]
forKey: NSURLIsExcludedFromBackupKey error: &error];
if(!success){
NSLog(#"Error excluding %# from backup %#", [URL lastPathComponent], error);
}
return success;
}
Hope that helps!
I assume the reviewer doesn't like that you are storing the database in the library folder and there within one created by you. If you read the mentioned guidelines you'll see that you shouldn't store there.
Data that can be downloaded again or regenerated should be stored in the /Library/Caches directory. Examples of files you should put in the Caches directory include database cache files and downloadable content, such as that used by magazine, newspaper, and map applications
I had this problem for a while also. So I made a class to handle this for me. There are different rules of where you can store stuff in different OS's. So my class checked the OS and returned a proper data director for each one and even handled the migration of data from one location to the other if the OS was updated.
But pretty much today you could just support the 5.1 and up location and be fine.
The key is that you need to set your do not backup attribute also.
I just put in a github here: https://github.com/badweasel/BWFileManager

iPhone core data preset

What is the best way to initialize Core Data database with content. My iPhone app will have a static database with products (data and images). How/where to store images, how to pre-populate database?
Here is what I did:
Create the database inside the iPhone app
I created the model in XCode and do a query against the database (this creates the database)
My static data is a CSV file
Use a Ruby script to read the CSV file
Use the ruby gem sqlite3 to insert data into the database
Copy back into project
Alternative:
Store the CSV/XML file containing data inside the app
Parse it on startup and create your NSMAnagedObjects
Tools/Resources:
Base software for editing/viewing a sqlite3 database
Database Location:
I'm afraid I don't remember it on the top of my head but when you use the simulator your application will be built and copied into a directory. I think the path to your application will be something like this. The database depending on how it is setup is usually in the Documents folder.
~User/Library/Application Settings/iOS Simulator/<version>/<app id>/
Reference the official Apple docs provide a way to prepopulate data into Core Data:
Core Data Books
Within my own application, I replaced the function 'NSPersistentStoreCoordinator' within AppDeledate with this one :
- (NSPersistentStoreCoordinator *)persistentStoreCoordinator{
if (_persistentStoreCoordinator != nil) {
return _persistentStoreCoordinator;
}
NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:#"CoreDataBooks.CDBStore"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:[storeURL path]]) {
NSURL *defaultStoreURL = [[NSBundle mainBundle] URLForResource:#"CoreDataBooks" withExtension:#"CDBStore"];
if (defaultStoreURL) {
[fileManager copyItemAtURL:defaultStoreURL toURL:storeURL error:NULL];
}
}
NSDictionary *options = [NSDictionary dictionaryWithObjectsAndKeys:[NSNumber numberWithBool:YES], NSMigratePersistentStoresAutomaticallyOption, [NSNumber numberWithBool:YES], NSInferMappingModelAutomaticallyOption, nil];
_persistentStoreCoordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel: [self managedObjectModel]];
NSError *error;
if (![_persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType configuration:nil URL:storeURL options:options error:&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.
Typical reasons for an error here include:
* The persistent store is not accessible;
* The schema for the persistent store is incompatible with current managed object model.
Check the error message to determine what the actual problem was.
If the persistent store is not accessible, there is typically something wrong with the file path. Often, a file URL is pointing into the application's resources directory instead of a writeable directory.
If you encounter schema incompatibility errors during development, you can reduce their frequency by:
* Simply deleting the existing store:
[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil]
* Performing automatic lightweight migration by passing the following dictionary as the options parameter:
#{NSMigratePersistentStoresAutomaticallyOption:#YES, NSInferMappingModelAutomaticallyOption:#YES}
Lightweight migration will only work for a limited set of schema changes; consult "Core Data Model Versioning and Data Migration Programming Guide" for details.
*/
NSLog(#"Unresolved error %#, %#", error, [error userInfo]);
abort();
}
return _persistentStoreCoordinator;
}
Hope this works, good luck!

Resources