Wednesday, November 21, 2007

Customizing errors generated by NSFormatters

I'm posting this because I was having a problem with errors generated by NSFormatters and saw a thread on it in the cocoa-dev archives that ended in a possible feature enhancement request with no Radar bug number to track.  After much experimentation, I think I've found a solution.

If you're working with NSFormatters (especially NSNumberFormatter), and you want some control over the default "Formatting Error" alert that you get when you give the formatter a string it cannot format, there is a way to do this.

In my situation, I had an NSTextField with an NSNumberFormatter attached to it.  The text field is bound to an attribute of a Core Data entity via NSObjectController.  The owner of the NIB file that contains all of this is an NSWindowController subclass.

At first, if I put a non-numeric string in the text field, I would get an application-modal alert stating "Formatting error" with the buttons "Discard changes" and "OK".  I'd much rather have something more descriptive, like "This field requires a numeric value".  If you override the willPresentError: method in the NSWindowController, you can inspect the NSError that is used to create the default alert.  You won't find anything too helpful, though.  The domain is NSCocoaErrorDomain, the code is NSFormattingError, and the userInfo dictionary has the "Formatting error" string, and an internal recovery attempter.

The solution I used, was to make the NSWindowController the delegate of the NSTextField, and to implement the NSControl delegate method:

- (BOOL)control: (NSControl*)control didFailToFormatString: (NSString*)str errorDescription: (NSString*)errDescription

From this method, you can inspect the string the user entered, and create your own NSError with a more descriptive message.  But, in order to get this custom NSError to appear instead of the standard one, you can use:

[control presentError: err]; return YES;

or for the alert to appear as a sheet:

[control presentError: err modalForWindow: [self window] delegate: nil didPresentSelector: NULL contextInfo: NULL]; return YES;

You need to return YES, or else you'll get both your custom error and the standard error.

If you need to determine which attribute was being edited, you can create IBOutlets in the NSWindowController, connect them to the fields with formatters, and then compare the control parameter against these outlets.

Hopefully this saves someone some time.

No comments: