samedi 15 septembre 2012

iCloud, UIManagedDocument and library-type apps

So I've been programming for a number of years now, and as with many other topics, the more you know, the more you realize you don't know. Perhaps a lot less if you are Aaron Hillegass, Joe Conway, Paul Hegarty or Ray Wenderlich and many more excellent Objective-C gurus from which I have learned various pieces of great knowledge over the years.

So much to learn, just following the changes in the Mac and iOS APIs!

Understanding APIs per se are not the greatest challenge though. Most of the time, you can figure out how to use those after looking around a bit. What I find challenging is discovering the best practices for various types of apps. Making the best assumptions before I start a project. By now, I've refactored so much code because of initial choices that I've stopped counting.

Like I said, I'm no expert. I've been programming for the Mac for many years, learning as I went, relearning all the time. At a slow pace at first and accelerating over the last couple of years as I jumped onto the iOS bandwagon, followed some courses and built a couple of intro courses myself.

Anyhow, I'm learning Core Data, UIManagedDocument, iCloud and some other little pieces of info around this these days and boy, it is not easy to figure out how to approach this. So I'm publicly posting this in order to:

1. Clarify my thoughts (nothing better than to explain to make sure you understand)
2. Get some reactions from peers on where I might be wrong, better approaches, etc.

This, I am sure, will be a great learning experience.

The goal
I'm building an app which will sync its single-document via iCloud (if available) on multiple iOS devices.

The tools
UIManagedDocument, to make things easy following both Paul Hegarty's suggestion in the CS193P Stanford course and the WWDC 2011 Core Data and UIDocument/UIManagedDocument sessions
CoreData (automatically part of the above)
A Library-Style app (a single instance of UIManagedDocument)
iCloud to optionally store this document

The Journey
So I read and listened to the CS193P lessons, WWDC sessions, re-read Joe Conway's excellent course, Ray Wenderlich's great tutorials and perused Stack Overflow to make up my mind on hw to approach this and started first migrating my current work-in-progress app to Core Data instead of a local monolithic archive. That wen't well. Now, I'm moving this to iCloud... If the service was always on, thre would be no issue, but the user being able to toggle it at any time is causing the questions I currently have.

Once all is running in iCloud, it's fairly straightforward. User creates data in one place, it appears on the other device. Almost no work once UIManagedDocument is used. Even conflicts are pretty much resolved automagically.

Now, what if the user decides to start using the app without iCloud, either because iCloud is not enabled, unavailable or turned off in my apps prefs (a best-practice according to Apple's documentation). Then, the app should create a local version, right? Cool.

Update: Some apps actually ask you up front if you wish to use iCloud or not. I might assume so on first run if iCloud is on, but leave it off if the user enables iCloud later.

Cloudy after sunshine
When iCloud later is turned on via the device settings or in the App, I figure unless it was specifically turned off for the app itself (via my apps prefs), this is an indication the user want to migrate their data to iCloud, so I'll assume I can move existing data to iCloud using the appropriate calls to NSFileManager. Pretty well documented. not so cloudy at this point I guess. This is all well if the user never had iCloud on when using this app before. No data exists yet. What if there is something though? I guess I'll have to check first if there is some data in iCloud and ask the user which data they want, the now local data or the potentially older iCloud data. Am I missing something? Sounds like the old MobileMe syncing UI... Or does UIManagedDocument handle this and try to sync the data, sending notifications or setting the state of the document accordingly?

Update: I'm pondering this. Should I auto-migrate the data with no warning or ask the user? How about a message which says "I notice you've enabled iCloud, would you like me to move your existing local data to iCloud? Then, what if there is already a version on iCloud?

Clearing up the sky

Then, what should I do if the user disables iCloud in the app or for the whole device after creating some data? If it is for the device (via Settings), they get a warning that local iCloud files will get deleted. They stay on iCloud, and can potentially be re-accessed later. In such a case, I could just create a new empty local database on the next launch, since there is no way I can move it back... That I know. Am I wrong? Would I get some last notification perhaps?

Update: I notice some apps seem to simply keep the iCloud version as their local version. Not sure how... I'll have to read some more.

Second Update: Don't always trust what others have done... some apps say things, but they don't actually work :-)

If the user turns iCloud off in my app's prefs, then I can handle moving the data locally which is really changing the location of the file, as only the log of changes gets stored in iCloud after the first move there. So if I did this, the user would still have their latest data. Will users actually turn off iCloud in their apps before turning it off completely? Will they perhaps be aware enough of all this not to turn it off?

And on top of all this, when the user creates a new file, I must not forget to delete any logs which would have stayed in iCloud, otherwise it will be impossible to sync anymore using this app. I got this from Rich Warren's book, Creating iOS 5 Apps: Develop and Design. It was probably in multiple other places, but I was ready for that info when I was reading his tutorial on UIManagedDocument.

So that's where I am... Trying to make sure I do this right both in the way I use the Frameworks and objects, as well as the user experience. As I discover more, I'll update and fix this so it can become an overview on how to best approach this scenario.

More updates: Found more info in the documentation in "Designing for Core Data in iCloud" especially in "Design the Launch Sequence for Your iCloud Core Data App" which help sunderstand how to structure this. Basically, you keep info in the UserDefaults so you can make the right choice later if iCloud availability changes or the user changes their mind.

- Renaud