I have database with some default content. How can I use iCloud to sync changes in database on different devices?
I know how sync devices if CoreData was empty first and I can migrate from external sqlite file to CoreData.
If you have an existing Core Data store and you want to add iCloud support, you'll need to transfer all of your existing data to a new data store and save the result. This is necessary because iCloud only generates transactions when you save changes-- so you need to effectively save changes for everything to jumpstart the syncing process.
In most cases you can do this in a single step by using NSPersistentStoreCoordinator's migratePersistentStore:toURL:options:withType:error:. Pass in the same iCloud options that you would use when calling addPersistentStoreWithType:configuration:URL:options:error:. Use this alternate approach only when moving from a non-iCloud data store to one that does use iCloud.
Also, beware of using iCloud with Core Data. It has not earned a reputation for reliability. Pay close attention to Apple's documentation and sample projects, and even then be prepared for it to just not work sometimes.
Related
I'm working on an Application that uses Core Data with iCloud (with the great improvement given by iOS7). This application stores data to describe a task with this information:
name a NSString
date a NSDate
image a NSString which describes a path to the picture
The pictures could be stored in Documents or Library Directory (I have to decide which is the more convenient folder), by the way, in the same folder with a unique name.
Now I'd like to activate iCloud sync for the images too otherwise the experience of the user will be incomplete (I just sync DB data.. no images, a strange/wrong behaviour for an app).
I'm really confused by Apple Documentation. I can't find a way to understand exactly how iCloud data works for this kind of needs. I just want to sync every file of a folder as soon as they will be created. So my questions are:
Could you share some good resources to learn how to use iCloud for file sync
Have I to use UIDocument and other iCloud API? or is there something "automatic". Quite a new bye/stupid question, I know :(
Are there any problem using Core Data and Document based iCloud synch in the same app?
Note: I know that I can sync data just by adding file in the document folder and hoping that users activate document sync... but this is not what I want obviously.
It is pretty straight forward to use both Core Data transaction log synchronisation and file based synchronisation in the same app to achieve what you want to achieve.
So you would set up your Core Data stack to use iCloud options and synchronise data changes via iCloud. At the same time you would store your images in the Apps iCloud container so they get synchronised as well. Just remember you need to use a relative reference to the images in your Core Data fileURL because the full pathname will vary depending on the device the app is running on. So for example you would just store the image filename in Core Data and use a standard directory such as 'iCloudContainer/Documents/Images/' to store them. 'iCloudContainer' being the URL you get by calling the [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:] API.
I have uploaded sample Core Data apps that use iCloud for transaction log synchronisation (i.e. synchronising data in Core Data) and that use file synchronisation for storing Core Data backup files in iCloud which can then be accessed by any device. You should be able to use the same code for moving backup files to and from iCloud for your images. Just remember you have to trigger download of files from iCloud before you can use them either by doing a coordinated read or by initialising the download using NSFileManager.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/sample-apps-explanations/
Download and run the sample apps and use the built in Backup File Manager to make backup files and to copy them to and from iCloud using different devices. Then just use the same code when storing your image files.
Your App does have to handle things like the user changing iCloud account, logging into or out of iCloud etc. and them move the core data file and image files accordingly.
The only way to have this happen automatically is to create a binary data attribute in your model for the images. If you do this, you will probably want to check the external binary storage allowed option, so the photos end up stored as files and not in the database.
If you would rather store the photos external to your store, you will have to do more work. You will need to migrate the photos into the iCloud container yourself, using the NSFileManager methods, for example. You could also use a class like iCloud Access if you find that easier.
The downside to handling the photos yourself is that you can never be sure that they have all arrived on your device when the Core Data store syncs, so it could be one or more photos are missing, even though there are entries for them in the store. You would have to make sure your app could handle this scenario, perhaps showing a placeholder image until the real photo was accessible.
There are no issues using Core Data and Document syncing in one app. In fact, they are exactly the same under the covers. From iClouds point of view, they are all just files to be transferred.
I'm working on a new app that uses Core Data and iCloud. I'm following the iCloudCoreDataStack demo and the iCloud Design Guide.
So far the synchronization between devices is working well, but I haven't figured out how to seed a small amount of data the first time the app is used on the user's first device and skip the seeding if the app is used on a second device (since it should download from iCloud instead).
This should be easy, just ask the iCloud Container if it has any data. Download the data if it exists or create new data if it doesn't.
But I couldn't find a way to do this :-(
I can think of three ways to solve this:
Use migratePersistentStore:toURL:options:withType:error:
I have a very small amount of data, so for this case this feels like overkill
Store a value on NSUbiquitousKeyValueStore to mark if the initial synchronization has been made
I tried using NSUbiquitousKeyValueStore, but sometimes it would take too long to get the value from
the UbiquitousKeyValueStore, so the seed data would be created even when not needed, resulting in duplicates.
Use a sentinel file to have the same effect of #2 (I'm not sure how to implement this)
The App is iOS 7 only and new, so there's no need to migrate old user data.
Every relevant tutorial and book that I found seemed to be using the pre-iOS7 super complex way of doing things (e.g. using a fallback store) that is not necessary on iOS 7.
Either I'm missing something (often the case) or this is more complicated than it should be.
I appreciate any suggestions and pointers.
It is never a good idea to seed a distributed datastore with an initial dataset. Generally this initial data can be packaged into a store file that is shipped with the application, and added as a second persistent store to the coordinator used by your application's managed object context.
That said, it is possible, although unwise to seed based on the completion of Core Data's initial import.
You need to wait for NSPersistentStoreCoordinatorStoresDidChangeNotification with NSPersistentStoreUbiquitousTransitionTypeKey set to NSPersistentStoreUbiquitousTransitionTypeInitialImportCompleted.
If there is no data in the store you can seed your initial data set at that point.
However it is important to realize that multiple devices could receive the initial import notification without importing the seeded data and thus seed it again. There is no way to avoid this.
On the point of shipping a second persistent store with your application, to serve as seed data.
This is accomplished as Marcus points out below by adding it as a read only store to the persistent store coordinator that is in use by your app's managed object context.
NSDictionary *options = #{ NSReadOnlyPersistentStoreOption: #YES };
[_psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:seedStoreURL
options:options
error:&localError];
NSDictionary *iCloudOptions = #{ NSPersistentStoreUbiquitousContentNameKey: #"storeName" };
[_psc addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:iCloudStoreURL
options:iCloudOptions
error:&localError];
_moc = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
[appMOC setPersistentStoreCoordinator:_psc];
This way your application's managed object context has access to data from both stores and will fetch both sets from fetch requests. The managed object context is also smart enough to automatically insert new objects into the correct store (because one of them is read only).
The only trick is that if you want your user to be able to modify data from the seed store you'll need to move those objects to the iCloud store.
This is a much easier approach than trying to migrate the dataset wholesale because ideally your user will only be using a single device at a time. In the case of a collision you'll at most have to resolve a few duplicate records as opposed to trying to detect duplication across the entire dataset.
Try this approach:
Before creating the local store check if one has already been created in iCloud by looking for the presence of a directory with the 'NSPersistentStoreUbiquitousNameKey' name in the iCloud /CoreData directory.
If one exists then this means that some other device has already created a store and shared it in iCloud so when creating your local store on the device don't add seed data because this will already exist in iCloud.
If one does not exist then no other device has shared the store in iCloud yet so you can probably create your seed data and sync to iCloud.
NOTE that the following scenarios are not catered for:
A user is running the app on a device that does not have iCloud
enabled - if the user chooses to now sync with iCloud from this
device you will have to deal with issues arising from trying to merge
data from this device with data already in iCloud. Once again you
can check for the presence of data in iCloud and then ask the user
whether they want to try merging data from the device or whether they
want to replace the data with data from iCloud.
A user is running the app on a device that is not connected to the
network - and has not had any data synced from iCloud yet so thinks
there is no file already in iCloud. The app will then create seed
data and when it gets a network connection Core Data will merge the
data - you app may have to deal with the ensuing problems.
For these scenario's you may need to try user education.
When core data syncs with iCloud, occasionally all of my records are doubled. I believe this happens on reinstall, where the app has not yet synced data, creates a new data set, and then iCloud syncs and there are double of each record for each model.
Is there a way to prevent this sort of behavior? Right now I'm checking every model on load and data sync for duplication, but this seems messy and hackish.
iCloud transfers data between devices asynchronously, and there can be quite some time between when you add data to the iCloud container, and when it actually gets transferred. Metadata is transferred between devices faster, but even this can be quite delayed.
The reason this is important is that you can never do a test on one device that will guarantee that data has not already been added to iCloud. One device may have seeded data, but not yet begun to upload its files/metadata. At that point, a second device cannot know about the seeded data from the first device.
Apple's advice is to handle this by de-deplicating your data after each merge. Each device just adds its own seed data, and if you find that it has been added twice, you delete half of it, being careful to ensure corresponding objects are deleted on each device.
This approach works, but feels a bit like a hack. An alternative, used in my Ensembles framework, is to supply global identifiers for your objects so that the sync framework can automatically import and merge seed data. That is the approach taken in Ensembles, and it makes seeding data quite a bit easier and less ad hoc.
Trying to use just an iCloud store is folly. Data must be stored both locally and in iCloud, or you will run into serious problems.
Use MagicalRecord if you want core data.
I'm using Simperium to sync instances of my app's data between each other. I went with this over iCloud because I've heard iCloud has a lot of issues with Core Data and as a novice to iOS, I believe Apple makes you pay to use their services (in a form of developer membership). Since my project is academic, there isn't a need for this.
I've set up Simperium to sync data from my app to their servers, and it's syncing well, but it seems it doesn't have support for binary data. My application syncs items and wishlists, and an item always has an image associated to it. The thumbnail I store in Core Data, since it's small (75x75), and the large image using NSFileManager. None of these sync.
Is it possible to enable sync for binary data and/or images stored in the app's sandboxed documents folder using Simperium? I've heard it's experimental, but if it works decently, I'll be glad to enable it. If not, are there any other frameworks that use sync and don't require iCloud to sync Core Data with binary data?
Syncing binary data isn't yet officially supported by Simperium, but you can track this GitHub issue for it. At the moment it won't work experimentally, either.
One strategy that some other developers are using is to sync filenames using Simperium (since they're just strings), and then deal with the corresponding data files on your own, either manually or with the help of another system like Amazon S3.
I want to make my app sync its SQLite Core Data store between devices using iCloud. Right now I let users manually sync their SQLite files using Dropbox, but that is cumbersome for users. I want it to be automatic.
What is the best practice to achieve this functionality? Do I need any special provisioning profiles?
I read somewhere that I need to set up my Core Data database to use UIManagedDocument but I haven't found anywhere that explains how to do this clearly. Any suggestions?
Turn on cloud syncing. That is step one. From there it depends on your application and its requirements.
Sorry for the short answer but your question is rather vague and you do not have any requirements for your app.
Assuming it is just a sqlite file all you need to do is turn on cloud syncing, change your Core Data stack to handle being async and Bob's your uncle.
Update
It is unlikely you will need to use UIManagedDocument. I would say that unless you have a kitchen sync type application you are fine using the Core Data ubiqutous store syncing at the NSPersistentStore level.
At this time, quite a few people are having issues with UIManagedDocument so I would avoid it unless you absolutely need to sync things that are not contained within the sqlite file.