I have been puzzling about retrofitting my app for iCloud for a few days and hope someone can help. After getting past code signing issues I am not sure I understand the model for incorporating iCloud. I'll tell you the problem I'm trying to solve first since I'm a big believer in telling people what I'm trying to do before having them try to fix the way I'm doing it :-)
My app workflow
User browses the store which lists a series of training plans they can download
User picks a plan and downloads it
I pull the training plan from our webserver customized to their needs
I add the filename for the training plan they downloaded to a plist of plans they own
User opens the training plan and sees the day-to-day schedule
I want the user to be able to do this on their iPhone and then open their iPad and see the exact same training plans synced over there.
My problem
I currently save the files to the Documents directory and that works just fine. How do I retrofit this to work with iCloud such that folks without iCloud enabled can continue to use the app but those who do get the added benefit?
My understanding
I'm confused as to whether I still need to save to Documents folder and then save a copy to the iCloud folder OR whether I just write to the iCloud folder from now on.
If it's the former I believe I just write a copy the Documents folder files to the iCloud area too to push it up but how do I detect a new file in the iCloud folder and copy it back to my Documents folder?
If it's the latter the files should just exist right?
I'm hoping it's the latter and that still supports devices without iCloud turned on...
Thanks for any help clarifying.
Adam
The iCloud API is pretty well documented and there is a specific chapter that deals with what you are after:
Managing the Life Cycle of a Document
A document goes through a typical life cycle. A document-based
application is responsible for managing its progress through that
cycle. As you can see from the following list, most of these
life-cycle events are initiated by the user:
The user first creates a document.
The user opens an existing document and the application displays it in the document’s view or views.
The user edits the document. A user may ask to put a document in iCloud storage or may request the removal of a document from iCloud storage.
During editing, saving, or other actions, errors or conflicts can happen; the application should learn about these errors and conflicts and either attempt to handle them or inform the user.
The user closes a selected document. The user deletes an existing document. The following sections discuss the procedures a document-based application must complete for these life-cycle operations.
In essence, you application is responsible for working out whether iCloud is available for a particular user and then confirm that the user wants to store their data in the cloud. Based on that selection you will need to work out how to move their existing data from the documents directory to a cloud URL.
On other devices that are setup to use iCloud storage, you have the option to discover documents available via a metadata query.
http://developer.apple.com/library/ios/#documentation/DataManagement/Conceptual/DocumentBasedAppPGiOS/ManageDocumentLifeCycle/ManageDocumentLifeCycle.html#//apple_ref/doc/uid/TP40011149-CH4-SW1
If you are looking for a very easy sample which implements iCloud, have a look at this:
iCloud basics and code sample
This gives you a concrete example of how to implement some of the stages Rog has cited in his post above. Hope this helps.
Related
Once again stuck in iCloud :(. I am using Core Data + iCloud and in my app i want to know if user switched off Document and Data from his account.
Thanks
There is no built-in support for detecting this change, only for detecting if the user has logged out. One common approach is to write a sentinel file to iCloud, separately from Core Data, and then monitor that file to see if it disappears. Details vary but it's usually something like:
Write the file using NSFileManager iCloud calls, and store the file name in user defaults. Each device using the account will have a different file, so including a UUID in the file name is a good idea. You only write this file, you never change it.
Use either NSFilePresenter or NSMetadataQuery to get notified of changes to that file-- like, if it disappears.
If the file disappears, take immediate steps to unload the entire Core Data stack, including every managed object, because they don't have a persistent store backing them up any more.
One sample implementation is in Black Pixel's version of the Core Data "Recipes" demo app. You might need to tweak it to get it to work right in your code.
There are two cases:
The app is not running, or in the background when the account status changes.
The solution here is to store / compare the identity token using NSUserDefaults (or some other storage local to the app). Grab the identity token from NSFileManager and compare it to the cached value each time the app finishes launching or comes back to the foreground.
id token = [[NSFileManager defaultManager] ubiquityIdentityToken];
The app is running when the account changes.
This requires a file presenter to "something" inside the container. The best solution is to use a file that is not synced.
Implement -accommodatePresentedItemDeletionWithCompletionHandler: to spawn a thread (dispatch_async works great) and do your cleanup work.
I just had a chat with a member of the App review team and I was told, that providing a DB 10 MB is far to large for an app to be approved. The lady told me, that the issue with my app is that I am not compliant with guideline 2.23 of the storage guidelines.
They, at least the person I talked to on the phone, claimed, that it an app copying a database on first launch of 10 MB will not be approved by Apple.
I am copying my Database which contains a set of > 50'000 records from the resource folder into the Library folder. I am segregating the databases into 2 DBs, one DB, the user is actually applying the changes, and another DB, where the user can copy data from.
Question: Is there another way instead of copying a DB from the resource dir? I decided the resource-folder to library-folder approach, as this is simply the fastest and seemed to me the most user-friendly approach. Of course I can make the user download the DBs from the internet. But this is yet another process step, which I personally would not like to undertake as an end-user.
I still don't have any specs from Apple, what the maximum allowed size of a first time launch DB is. Don't know, why Apple makes such a fuzz about this information.
Anyway, I would really appreciate your comments and possible solution approaches.
René
I may mention here, that my solution to the issue was, not copying the static database at all. I simply leave it in the bundle and read the data from there. As it is read-only data the app is fine like this. And Apple was happy. Got it approved after that change. Thanks to all for the support.
René
I had a similar issue. The correct approach to take here is to move the file to Library/Caches folder. That folder is cleaned up when there is low memory situation and also never backed up to iCloud. I have an app which has 36 MB of database file. But I dont bundle it with the app. When the app is opened for the first time The app tells the user to download the DB once. This way the app size is drastically reduced.
Any specific reason why you are bundling the db with the app?
I want to have on-the-device database for my iphone app. It concerns with the data, which comes from dictionary consisting of 200.000 things and their definitions. It is only related with text-type data as appeared. My questions:
1- Does SQLite hold all of these data?
2- When the client downloaded my app, he/she will also have the db in his/her device. Does app store allow me to update my app's db and upload my new release? (i don't know these issues well by the way)
3- And can any client, who downloaded my app, hack and obtain my database? Is there any prevention methods? Is SQLite resilient enough against these?
1- Does SQLite hold all of these data?
Yes, SQLite can cope with this amount of data.
2- When the client downloaded my app,
he/she will also have the db in
his/her device. Does app store allow
me to update my app's db and upload my
new release? (i don't know these
issues well by the way)
The general approach is to store the SQLite database in your application bundle and then copy the database into the application's document directory on the device when the application is first run. On subsequent updates to your applciation, you should check if the database within the document directory is the same version and update it if necessary. See the existing Run NSBundle from the documents folder question/answer for more information on this.
3- And can any client, who downloaded
my app, hack and obtain my database?
Is there any prevention methods? Is
SQLite resilient enough against these?
It's fairly trivial to open up an app (the deployment package is just a zip file after all), so yes, it will be possible to obtain access to your database data. Unfortunately there's no easy way around this that I'm aware of. (You could I suppose download the data from a server when you first run you app, but it'll still be accessible on a jailbroken device.)
Sometimes, you just have to bite the bullet and accept the fact that your data is going to be ripped off.
1) sqllite can definitely hold that amount of data.
2) You can put up an option of refreshing the database in your app. That can be used to sync the local db with the server copy. Updated db can also be added with the new version of the app.
3) You can encrypt your local db using SQLCipher for protecting your application db against hacks.
I'm using MonoTouch and also System.Data to create a DataSet (just xml to those not familiar) for simple data binding. Data on my app is minimal so no need to go all out with SQLLite. The dataset use makes it easy to pass via web services for cloud sync.
I serialize the DataSet to the personal folder on save and of course read this file when the app starts up to load up the user's data. I've had issues where this file is becoming corrupt and I'm not sure why. I assume file I/O may be slow on these devices and that could be the cause, I'm not sure, but it is happening.
I'm also concerned that maybe iTunes is passing this file back and forth between the PC/MAC when the user syncs their devices with iTunes, which may be the cause of the corruption?
I want to prevent this device file from syncing with iTunes and also reliably persist it. I'm using the NSFile.Save option to save it to the device. I'm thinking since it's a text file maybe I could more safely store it in the standard user settings area instead? This would prevent it from being synced by itunes, I presume?
What is the most reliable and safe way to handle this file i/o for the xml dataset storage?
Thank you.
You're using MonoTouch. Isn't it simply a matter of calling DataSet.WriteXml() with a FileStream object ready to write to a document in your Documents folder?
That Documents folder is backed up to iTunes. It's not synced, but it helps if your user is restoring their phone (because they bricked it, lost it, whatever). It doesn't explain why it's corrupt.
The only thing that I can think of why it's corrupt is because it took too long for your app to write it. There's a limited time from the point where the user exits the app until it's closed down, to prevent apps from keeping the system hostage and deteriorate user experience.
If writing the whole dataset takes too long, you want to think about minimizing that. Perhaps you can just store the data, and not the schema. Or you can devise a way to store only the deltas on exit and reconcile when the user has loaded your app again.
You can also prevent complete loss of data by writing to a second file, and when that operation completes delete the old file and rename. That way, the next time you start up if the write operation didn't complete, the old file would still be there and the user would have only lost their more recent changes.
In any case, if your data gets too big for a simple write operation to complete, you should look at different options such as sqlite.
Your best bet is probably to just save the XML as text. It's as simple as File.WriteAllText(...) - there's no reason to go to NSFile for this. That's part of the advantage of MonoTouch :)
Regarding syncing, here's the rule:
If you keep the file in the user's documents folder (Environment.SpecialFolder.MyDocuments and Environment.SpecialFolder.Personal BOTH point to the user's doc folder), then it's going to get backed up whenever the user syncs with iTunes.
There's nothing wrong with this. It persists the data between sessions and makes it recoverable if something goes wrong with the user's phone and they need to restore from a backup. Since your question is about persisting an XML file on the phone, this is what you want.
As for the iTunes question, there's no problem with speed and syncing because your app isn't going to be running while the phone is syncing. The file will either have been saved or it won't. Any corruption that takes place is happening while your app is running.
Reasons for files getting corrupted can include:
Not saving before the user quits. You get a chance to do this.
Not gracefully handling an incoming phone call. The system warns you about this as well.
iTunes definitely isn't corrupting your file. If that were the case, iOS apps would all be broken. It could be happening on your dev machine for whatever reason, but I've never seen this happen elsewhere, and I've done quite a bit of iOS development.
If you'd like a tutorial on reading and writing files, I posted an answer in another question.
It's lengthy, but the point was to answer as many questions as I could so nobody would be left hanging or confused.
A nice thing about iOS devices is that you're back (for most apps) in the one-person-at-a-time world. You're writing apps where you don't have to worry about 5,000 people trying to use your web-based app at the same time (that's not always true, but... you get the point). As a result, you can do things that you might normally consider bad for performance, but you're unlikely to see any performance problems (as long as the file you're saving is either small enough to be saved quickly or you're saving in the background on another thread - you never want to block the main (UI) thread with a heavy IO operation).
If you have any more questions, feel free to ask.
I hope this helps :)
Lots of frameworks are read-only, but I've found that GDataXMLNode from http://code.google.com/p/gdata-objectivec-client/ works very well read/write.
Having said that, on the iPhone you'd do yourself a big favour using Core Data with a SQLLite backend. :-) Apple has done all this for you and optimized it more than any of us every will
Cheers
Nik
Consider using SQLite, I'd go for something like
http://www.taimila.com/entify/ (not tried yet)
catnap orm
or Faks sqlite-net on google code (using this in a few apps)
entify - if it does what it says it can do - looks really good.
persisting XML on the iPhone as a means to store and access data is a nause you dont want to get into. I wrote about it here http://iwayneo.blogspot.com/2010/08/festival-star-history-serialization-as.html
I have a content based, read-only iPhone app. Users can select favorite topics, which I need to track. Some topics I'd like to make available between app updates through the App Store. I'll need to track if users have downloaded these particular topics or not until the App Store update is available. This approach will consist of two tables for user tracking. All other tables contain mainly static content, save any new downloaded entries.
Before I began tracking user content, I'd always deploy the database on app updates. An overwrite - simple. But now I need to track certain user configurations. Rather than trying to keep track of which app version a user has and running through a list of sql scripts in the correct order, so the user is at the right database version, I'm thiking to use two databases. One contains static content and the other user data. The static content database is always overwritten. That keeps things simple. The database currently is 250kb. It will grow very slowly.
I have plans to use SDK 3.0 push notification and peer-to-peer as well, which will store any user config data in the user database.
Any one see problems with this approach?
This sounds alright to me. If you're using SQLite, you may want to look into the ATTACH DATABASE command, which lets you keep two databases open on the same connection.