CausticMango

Man, I love writing software. I should do it more.

Retrofitting iCloud to Core Data

With GameFriends in the app store queue, it’s time to start on v2.0, the main feature of which is iCloud support.

The app was written to use Core Data in a “library style” where there is a single object store for the entire app. iCloud supports this type of application by storing the updates logs in iCloud. Each instance of the app then updates its local store using the logs that iCloud propagates.

This turns out to be extremely simple to implement; an Apple engineer even did an awesome job of updating the Recipes sample app to work this way.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// get the ubiquity container
NSURL *ubiquityURL = [[NSFileManager defaultManager] URLForUbiquityContainerIdentifier:nil];
NSURL *ubiquitousStoreURL = [ubiquityURL URLByAppendingPathComponent:@"ListTracker.dat"];

// more core data stuff here ...

// add it to the options dictionary passed to the coordinator
if (ubiquityURL != nil)
{
    // yeah, looks like we've got iCloud
    [options setValue:@"List Database" forKey:NSPersistentStoreUbiquitousContentNameKey];
    [options setValue:ubiquityURL forKey:NSPersistentStoreUbiquitousContentURLKey];
}

// continue setting up the coordinator ...

Then you register for notifications for updates when you create the context:

1
2
// do this when you create the managed object context
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(mergeChangesFrom_iCloud:) name:NSPersistentStoreDidImportUbiquitousContentChangesNotification object:coordinator];

The final bit is to respond to the notification by merging the changes from the logs into the context, kind of like this:

1
2
// and do this in the notification handler
[context mergeChangesFromContextDidSaveNotification:notification];

Unfortunately, it seems like it doesn’t quite work reliably, yet. After using the app for a bit the logs get out of sync and no more updates propagate. Bummer.

So I’m having to refactor the app to use UIDocument instances. These seem to be rock solid & fast. It’s just more work than I anticipated since Core Data give you a lot of extra goodies you’ve got implement yourself with out it.

Ah well …

Textual

I’ve been a loyal user of Colloquy for years, now. I even bought the iOS version. But it’s frankly kind of … abandoned. I know there are devs still working on it, but it’s not getting much love. I guess you could argue that it’s more or less feature complete and doesn’t need any updates, but especially in the era of Lion and soon Mountain Lion it’s looking old.

The guys in the 5by5 IRC channel recommended Textual and I cloned the github repo, built it and have been loving it ever since.

First Bug (Fix Submitted)

Update: Easy fix; a single line of code. Version 1.1 was submitted to the store for review at 7:30pm.

Oops! Looks like Game Friends on the iPad doesn’t save the notes field. Working on a fix; should be in for Apple to review tonight or tomorrow.

GameFriends Approved!

Yeah! GameFriends is live today in the AppStore. It was a lot of fun to make (even though it took way longer than I expected). I hope people find it and thing it’s useful.

Lots of thanks to people who helped me beta test for the last few months:

  • Bankai Fourty Four
  • Barbara S.
  • Caroline P.
  • Janet H.
  • Jenn N.
  • Kevin K.
  • Lesley L.
  • Mike L.
  • Sue K.

Please tell your friends!

Hello, Octopress

Farewell, MobileMe. I know a lot of people complained about the service, but it always seemed to work great for me. I guess I was either just lucky or maybe not very demanding.

Doesn’t matter now, though, because it’s going away including iDisk and static web hosting.

I ran across Octopress the other day and was instantly taken with it. I’d been thinking about using Second Crack since it’s text editor friendly and generates a static site, but Octopress does the same running in a simple, Rake compatible, Ruby site.

Now, to convert all my old posts … or not.

Toolbar Troubles

Or, how to waste a lot of time on something really simple.

I can’t believe how much time I wasted trying to figure this out. It’s embarrassing, really.

Anyway, it turns out that if you want to change the items in a toolbar for a view controller, you can’t just create a mutable array, switch items around, and reassign it to the controller. You have to assign the items to a different array.

For example, this doesn’t work:

1
2
3
4
5
6
7
// assume we setup the items array and used it to populate the toolbar earlier
// and we just want to change it here

[self.items replaceObjectAtIndex:0
                      withObject:aFlag ? buttonA : buttonB];

[self.toolbar setToolbarItems:self.items animated:YES];

It doesn’t work because the array, self.items, is the same object. The toolbar just ignores it. To get it to work, you have to change the array the toolbar referenced. Like so:

1
2
3
4
5
6
7
// grab a mutable copy of the current set, change it, then put it back
NSMutableArray *items = [self.toolbar.items mutableCopy];

[items replaceObjectAtIndex:0
                 withObject:aFlag ? buttonA : buttonB];

[self.toolbar setToolbarItems:items animated:YES];

Adventures in CoreData

I was changing one of my iOS apps to use a NSFetchedResultController instead of a sorted NSArray of NSManagedObject instances (I really like the way it handles sorting and grouping, additions and deletions) and ran into a bit of a snag. Trouble is, the list is manually sortable by the user using the drag controls in edit mode.

The way you do this, of course, is by adding a numeric “sort” attribute to the entity and sort by it. When you move a row, you renumber the rows by their sort attribute.

If you’ve ever tried to do this with a UITableViewController using an NSFetchedResultController, you’ll no doubt be shaking your head right now because you’ll know this leads to very strange results. Do a search online and you’ll find tons of detailed advice for how to resolve this “problem”.

All of it is wrong. Tragically, comically, desperately wrong. Alway, always, always read the documentation first!

It turns out the cause of the odd behavior is very simple; the controller is observing the entities, recognizing changes, and translating them to table updates by calling the delegate methods. Normally, this is exactly what you want. The table will automatically adjust to changes in the fetched data. Except, if the table itself initiates the change, the two mechanisms compete with each other.

The solution to this strange behavior is simply ignore the delegate callbacks if the changes come from the table being edited. Set a flag in the tableView:moveRowAtIndexPath:toIndexPath: callback and if set, bail out of the NSFetchResultControllerDelegate methods.

Just goes to show you; don’t trust the Internet. Especially for coding advice.

I Love You, Xcode

Auto incrementing build numbers. See what happens when you use a real, Unix-based OS?

  1. Select the project in Xcode.
  2. Select the target.
  3. Click Build Phases, then Add Build Phase, and Add Run Script.
  4. Paste the script below.
buildNumber=$(/usr/libexec/PlistBuddy -c "Print CFBundleVersion" ${PROJECT_NAME}-Info.plist)
buildNumber=$(($buildNumber + 1))
/usr/libexec/PlistBuddy -c "Set :CFBundleVersion $buildNumber" ${PROJECT_NAME}-Info.plist