Cocoa Programming for Mac OS X - Aaron Hillegass [104]
Implement the Basic Formatter Methods
Edit ColorFormatter.m to look like this:
#import "ColorFormatter.h"
@interface ColorFormatter ()
- (NSString *)firstColorKeyForPartialString:(NSString *)string;
@end
@implementation ColorFormatter
- (id)init
{
self = [super init];
if (self) {
colorList = [NSColorList colorListNamed:@"Apple"];
}
return self;
}
- (NSString *)firstColorKeyForPartialString:(NSString *)string
{
// Is the key zero-length?
if ([string length] == 0) {
return nil;
}
// Loop through the color list
for (NSString *key in [colorList allKeys]) {
NSRange whereFound = [key rangeOfString:string
options:NSCaseInsensitiveSearch];
// Does the string match the beginning of the color name?
if ((whereFound.location == 0) && (whereFound.length > 0)) {
return key;
}
}
// If no match is found, return nil
return nil;
}
- (NSString *)stringForObjectValue:(id)obj
{
// Not a color?
if (![obj isKindOfClass:[NSColor class]]) {
return nil;
}
// Convert to an RGB Color Space
NSColor *color;
color = [obj colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
// Get components as floats between 0 and 1
CGFloat red, green, blue;
[color getRed:&red
green:&green
blue:&blue
alpha:NULL];
// Initialize the distance to something large
float minDistance = 3.0;
NSString *closestKey = nil;
// Find the closest color
for (NSString *key in [colorList allKeys]) {
NSColor *c = [colorList colorWithKey:key];
CGFloat r, g, b;
[c getRed:&r
green:&g
blue:&b
alpha:NULL];
// How far apart are 'color' and 'c'?
float dist;
dist = pow(red - r, 2) + pow(green -g, 2) + pow(blue - b, 2);
// Is this the closest yet?
if (dist < minDistance) {
minDistance = dist;
closestKey = key;
}
}
// Return the name of the closest color
return closestKey;
}
- (BOOL)getObjectValue:(id *)obj
forString:(NSString *)string
errorDescription:(NSString **)errorString
{
// Look up the color for 'string'
NSString *matchingKey = [self firstColorKeyForPartialString:string];
if (matchingKey) {
*obj = [colorList colorWithKey:matchingKey];
return YES;
} else {
// Occasionally, 'errorString' is NULL
if (errorString != NULL) {
*errorString = [NSString stringWithFormat:
@" is not a color", string];
}
return NO;
}
}
@end
Build and run your application. You should be able to type in color names and see the background of the BigLetterView change accordingly. Also, if you use the color well, you should see the name of the color change in the text field. Try typing in a string that is not a color.
The Delegate of the NSControl Class
Note that the bindings mechanism makes a nice Alert sheet when the formatting fails. The text field’s delegate can also be informed of the failed formatting. If the formatter decides that the string is invalid, the delegate is sent the following error message:
- (BOOL)control:(NSControl *)control
didFailToFormatString:(NSString *)string
errorDescription:(NSString *)error
The delegate can override the opinion of the formatter. If it returns YES, the control displays the string as is. If it returns NO, the delegate agrees with the formatter: The string is invalid.
Implement the following method in TutorController.m:
- (BOOL)control:(NSControl *)control
didFailToFormatString:(NSString *)string
errorDescription:(NSString *)error
{
NSLog(@"TutorController told that formatting of %@ failed: %@",
string, error);
return NO;
}
Now open MainMenu.xib and make the TutorController the delegate of the text field (Figure 26.9).
Figure 26.9. Connect the Text Field’s delegate Outlet
Build and run your application. When validation fails, you will see a message on the console indicating what the string was and why it failed.
Checking Partial Strings
You might want to create a formatter that prevents the user from typing letters that are not part of a color name. To make the formatter check the string after every keystroke, implement the following method:
- (BOOL)isPartialStringValid:(NSString