Karelia and I Open Source Update
It's taken us a lot of time to get round to it, but Karelia are now publishing some open-source of our open source code to a proper repository. Github no less!
This means KSExtensibleManagedObject has a new home: http://github.com/karelia/KSExtensibleManagedObject
And we have a new project: KSHTMLWriter. If you ever need to generate XML or HTML in your application, this may well your best bet. It certainly scales a lot better than +stringWithFormat:! Hopefully the documentation on github is good enough for anyone to get started.
All code's under a standard BSD license, and we encourage anyone to contribute towards the projects. There should be more coming over the next few months as we have it ready.
Tentative open-source steps
Over the last couple months, I've been quietly publishing to my BWToolkit fork. I thought I'd take the time today to write a brief post on what's there. Hopefully at some point Brandon will merge some of these back in to his master.
BWTabViewController
Like UITabBarController but for the Mac. There's no UITabBar equivalent yet; instead this matches the contents of an NSTabView with the viewControllers array.
The Interface Builder plug-in is incomplete — you can drag the controller in from the BWToolkit palette, but no functionality is exposed there other than NSViewController's built-in properties.
To use, you can let the controller build its own TabView by calling -view. Or, connect to an existing TabView (in a nib or with a -setTabView: call) whereupon it will take over control of that view.
BWToolbarPullDownItem
There seem to be dozens of popup/pulldown toolbar item implementations across the web, so of course I had to add one more! More seriously, all those I've seen seem to fall down by customising the popup behaviour more than I'd like. This one is very clean.
No Interface Builder plug-in yet, so create programatically and to a toolbar. For the most part, existing NSToolbarItem methods do everything you want, but to populate the menu:
[[item popUpButton] addItemWithTitle:@"foo"];
BWIWorkPopUpButton
Used by BWToolbarPullDownItem in its implementation. Like a regular NSPopUpButton, but when borderless expands the image rect and draws its own custom arrow at the bottom-right.
Three years
Today marks three years of my full-time employment at Karelia. We haven't achieved all that much yet. But just you wait.
Easier debugging of Core Data errors
Prompted by a friend's tweet (sorry it's late Kevin!) I was reminded of the code we've implemented to make life more pleasant when Core Data throws an error your way.
Problem 1: The alert sheet doesn't reveal enough detail
It's understandable; your customers don't care which bit of the model failed validation, they want a more helpful answer supplied by the application – or even better the error not to occur in the first place! But what about you, how do you dig into the error for more detail?
One option is to place a breakpoint on -[NSApp willPresentError:]. From there, introspect the error object in the debugger.
But this is somewhat of a pain. Getting hold of the error object in the debugger is tricky for a start! Somewhat more palatable is to log such errors to the console (probably for debug builds only, but up to you).
A nice convenient location is your app delegate:
- (NSError *)application:(NSApplication *)theApplication
willPresentError:(NSError *)error
{
// Log the error to the console for debugging
NSLog(@"Application will present error:\n%@", [error description]);
return error;
}
Problem 2: There's no easy way to see the entirety of an error's contents
The above snippet uses the built-in -[NSError description] method. It's pretty informative, but Core Data has a tendency to construct several, nested error objects. Particularly if there are multiple validation errors. Try this little beauty out in a category on NSError:
- (NSString *)debugDescription;
{
// Log the entirety of domain, code, userInfo for debugging.
// Operates recursively on underlying errors
NSMutableDictionary *dictionaryRep = [[self userInfo] mutableCopy];
[dictionaryRep setObject:[self domain]
forKey:@"domain"];
[dictionaryRep setObject:[NSNumber numberWithInteger:[self code]]
forKey:@"code"];
NSError *underlyingError = [[self userInfo] objectForKey:NSUnderlyingErrorKey];
NSString *underlyingErrorDescription = [underlyingError debugDescription];
if (underlyingErrorDescription)
{
[dictionaryRep setObject:underlyingErrorDescription
forKey:NSUnderlyingErrorKey];
}
// Finish up
NSString *result = [dictionaryRep description];
[dictionaryRep release];
return result;
}
Did you know that when you do po foo in the debugger, the string printed is actually generated by calling -[foo debugDescription]? So this neatly means that po error will give you better results.
And of course, you'll want to change the error presentation snippet at the top of the page to log using -debugDescription.
An alternative way to disable undo registration when using Core Data
As previously discussed, Core Data takes a little coaxing if you want to disable undo registration. The standard approach is:
[[self managedObjectContext] processPendingChanges]; [[[self managedObjectContext] undoManager] disableUndoRegistration]; // Make your special changes to the managed object [[self managedObjectContext] processPendingChanges]; [[[self managedObjectContext] undoManager] enableUndoRegistration];
The main point is to force through any changes the context has pending while the undo manager is still receptive, and then do the same before enabling registration again.
But what if you have other objects that have similar pending changes? Perhaps multiple MOCs attached to a single undo manager, or a custom class of your own. Or even just don't have a reference to the context itself handy. Well here's a simple alternative:
[[NSNotificationCenter defaultCenter] postNotificationName:NSUndoManagerCheckpointNotification object:undoManager]; [undoManager disableUndoRegistration]; // Make your special changes to the managed object [[NSNotificationCenter defaultCenter] postNotificationName:NSUndoManagerCheckpointNotification object:undoManager]; [undoManager enableUndoRegistration];
All managed object contexts observe their undo manager's checkpoint notification since that is the correct time for them to perform standard processing of pending changes. By posting the notification yourself, it's just an easy way to flush any changes from all relevant objects.

