iPhone Core Data/undo gotcha of the day.
In general, I like the NSUndoManager functionality in iOS, but sometimes the secret handshakes you need to know can really get me down. For example, I needed to disable undo/redo when setting a particular property of a model entity. Reading the documentation, this seemed pretty straightforward:
[[theApp.managedObjectContext undoManager] disableUndoRegistration]; detailItem.fieldImageFile = relativePath; [[theApp.managedObjectContext undoManager] enableUndoRegistration];
Seemed very clear, but it didn’t work! So, after much poking around on the Internet, I found a posting that alluded to the fact that changes to undo don’t occur until the run loop executes. So, to get the desired effect, I ended up with the following code:
[[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate date]]; [[theApp.managedObjectContext undoManager] disableUndoRegistration]; detailItem.fieldImageFile = relativePath; [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate:[NSDate date]]; [[theApp.managedObjectContext undoManager] enableUndoRegistration];
Yet another head-scratching API decision from Apple.
Key-value Observer (KVO) on the iPhone.
Here’s one for the you-learn-something-new-every-day file. Objective C supports a key-value-observer model that lets you monitor changes on an object’s property values. Unfortunately, I found this out the hard way while working on a MKMapKit project.
What I wanted to do seemed simple: have a map view with a bunch of moving objects on it. Each object pulls its current position from an XML document on the web. I already had my XML-wrapper object written when I wanted to display it on the map, so I simply implemented the MKAnnotation protocol. All of my objects appeared, and all seemed to be right with the world.
Then, I started updating my objects and found that the annotations didn’t move. At all. I found a bunch of people on the web trying to solve the same problem, but I wasn’t very happy with the solutions I found. Most of them involved removing the annotations and adding them back into the map view. But, thankfully, I found one article that mentioned KVO on the iPhone. I was pretty familiar with KVO from my Flex projects, but didn’t even realize it was supported in Objective C.
To make a long story short, if you don’t use the willChangeValueForKey and didChangeValueForKey before and after updating your coordinate property on your annotation object, the MKMapView will not be aware that it has moved. After bracketing my XML update code with those two calls, all of a sudden my annotations started moving around.
-(void)xmlProperty:(NSString *)newXml
{
[self willChangeValueForKey:@"coordinate"];
[xml release];
xml = [newXml retain];
[self didChangeValueForKey:@"coordinate"];
}
Exporting from Core Data on iOS.
So, I’m working on the latest release of our scrapbooking app (Coolibah) and I needed to export some objects that are stored in SQLLite through Core Data to my server. After googling around for a while (and finding not much, it seems like search results have started to really suck lately) I had to strike out on my own and work something up. I got to use a very cool Objective C feature that I’ve only recently discovered: categories.
I was able to extend the NSManagedObject class and add a new xmlString property. By using the entity property to do some simple introspection, I was able to write a serialization function in about 50 lines of code. Enjoy!
NSManagedObject+XMLSync.h
// // Created by Scott Means on 1/5/11. // Released into the public domain without warranty. #import <CoreData/CoreData.h> #import <Foundation/Foundation.h> @interface NSManagedObject (XMLSync) @property (nonatomic, readonly) NSString *xmlString; @end
NSManagedObject+XMLSync.m
//
// Created by Scott Means on 1/5/11.
// Released into the public domain without warranty.
#import "NSManagedObject+XMLSync.h"
@implementation NSManagedObject (XMLSync)
- (NSString *)xmlString
{
NSEntityDescription *ed = self.entity;
NSURL *uri = self.objectID.URIRepresentation;
NSMutableString *x = [NSMutableString stringWithFormat:@"<%@ id=\"/%@%@\"",
ed.name, uri.host, uri.path];
for (NSString *a in ed.attributesByName.allKeys) {
id value = [self valueForKey:a];
if (value) {
if ([value isKindOfClass:[NSString class]]) {
[x appendFormat:@" %@=\"%@\"", a, value];
} else {
if (![value respondsToSelector:@selector(stringValue)]) {
NSLog(@"no stringValue");
}
[x appendFormat:@" %@=\"%@\"", a, [value stringValue]];
}
}
}
bool hasChildren = NO;
for (NSString *r in ed.relationshipsByName) {
if (!hasChildren) {
[x appendString:@"/>"];
hasChildren = YES;
}
NSRelationshipDescription *rd = [ed.relationshipsByName objectForKey:r];
if (rd.isToMany) {
hasChildren = YES;
[x appendFormat:@"<%@>", r];
for (NSManagedObject *c in [self valueForKey:r]) {
[x appendString:c.xmlString];
}
[x appendFormat:@"%@>", r];
}
}
if (hasChildren) {
[x appendFormat:@"%@>", ed.name];
}
return x;
}
@end
Next project: figure out how to do better code formatting in WordPress!
Core data model migration on the iPhone.
Doing something as simple as adding a new attribute to an entity in your Core Data model will break your application when it comes to opening older persistent data stores. Core Data has some support for automatic migration, documented in the “Introduction to Core Data Model Versioning and Data Migration Programming Guide” (whew!) in the Lightweight Migration section. Unfortunately, there are crucial steps that are not mentioned in this section. Kudos to Grouchal on Stack Overflow for giving us the rest of the story.
Roll-your-own iPhone framework.
After starting work on my latest iPhone app (#6 or so), I finally decided it was time to get more efficient with my existing library of code. I’ve been building various helper classes for manipulating bitmaps, etc, and I wanted to be able to share them between my apps in a more organized way.
As usual, there isn’t much on the web about this, but I eventually tracked down a couple of useful articles to get me started:
The first article shows you how to add your own custom project type template to XCode so you can easily create shared iPhone libraries. The second one shows you how to reference your new library from your application. With a little trial-and-error I now have a nice shared lib of iPhone classes. Pretty soon, who knows, maybe Apple will allow dynamic linking and we can move iPhone development into the 80s!
