The Register® — Biting the hand that feeds IT

Feeds

String theory and the OS X AppKit

Colorful language

Agentless Backup is Not a Myth

Mac Secrets Much of the time, we're off exploring some framework such as GraphKit or the media browser. This time it's the turn of AppKit itself, the framework that's guaranteed to be used by every self-respecting Cocoa app. Strange as it may seem, there are plenty of undocumented goodies here, too.

Let's start with localization, This is the process whereby you build a Cocoa application in such a way that it will display any needed text strings in the default language set by the user. Ordinarily, this is done through judicious use of macros such as NSLocalizedString, the various foreign language strings being located inside the appropriate ".lproj" directory.

However, for those who are lazy and like to live dangerously AppKit provides a routine which provides effort-free localization, provided that the string you want to localize is one of the ones provided. First, you need to define the routine, _NXKitString, like this:

extern NSString * _NXKitString (NSString * tableName, NSString * keyName);

Because _NXKitString (note the underscore) is part of AppKit, there's no need to link with any other libraries or frameworks. The routine takes two parameters, the name of a string table, and an identifying key within the table. It returns an autoreleased string, localized for the current language. You'd use it like this:

NSString * cancelString = _NXKitString (@"Preferences", @"Cancel");

Thus, you'll get back "Cancel" where English is the default, "Annuler" in French, and so on. Of course, this will only work as long as the string you want is contained in one of AppKit's internal string tables. To see what's there, navigate to the following location:

/System/Library/Frameworks/AppKit.framework/Versions/C/Resources/

From there, you can navigate into the English.lproj directory, for example, where you'll see a list of string table files, all of which have the suffix "strings". These represent the allowable table names that you pass to _NXKitString. So, "Preferences" maps to Preferences.strings and so on.

If you examine the contents of each string table file - they're just plain-vanilla text files - you'll see the available keys for each table.

If your application has a very simple, restricted interface, you may be able to get by with just the available strings. It would be nice if "Yes" and "No" were included somewhere, but I haven't found them yet. One particularly useful table is "FunctionKeyNames". Here, you'll find many keystroke reference strings.

App Kit Calls

The _NXKitString mechanism is used widely in AppKit when displaying localized strings

Color Conversions are a hot topic and an oft-repeated plea amongst novice Cocoa developers is a way of converting from an NSColor to a CGColorRef. At least, if you Google for it, you'll see this question popping up everywhere.

It turns out that converting an NSColor object to the equivalent CGColorRef is deliciously simple - if you're in the know. I found an internal AppKit routine that pretty much does this job for you. Unfortunately, you can't call it directly from Cocoa-land because it's not a public symbol exported by the AppKit framework. However, what the routine does is very straightforward: it works by calling an undocumented method on the NSColor class.

If you were to come up with an equivalent routine of your own, it might look something like this:

CGColorRef CGColorFromNSColor (NSColor * color)
{
        CGColorRef result = nil;
        if ([color respondsToSelector: @selector (_createCGColorWithAlpha:)])           result = [color _createCGColorWithAlpha: [color alphaComponent]];
        return result;
}

As you can see, this code is very straightforward: we just check the presence of the undocumented selector, returning nil if the passed-in NSColor instance doesn't respond to it. In order to prevent the compiler from generating warnings, you'll also need to convince it that NSColor responds to the undocumented selector, providing an interface declaration like this:

@interface NSColor()
- (CGColorRef) _createCGColorWithAlpha: (float) alpha;
@end

You might wonder why the respondsToSelector: check is necessary? Well, for starters, it's wise to build such checks into your code when using undocumented routines. Also, it just so happens that NSColor is an example of a class cluster. In case you're unfamiliar with this term, a class cluster is a group of inter-related private classes, all of which descend from a public abstract class, in this case, NSColor.

Other examples of class clusters include NSArray, NSData and NSString. It might surprise you to know that NSColor is an abstract class, but in a real sense it is. For example:

[NSColor blueColor];                    // returns NSCachedRGBColor
[NSColor _blueControlTintColor];                // returns NSCachedDeviceRGBColor
[NSColor toolTipColor]                  // returns NSDynamicSystemColor

The point here is that all the class names mentioned on the right are descendants of NSColor; you will get different descendant classes returned to you depending on what type of color you request.

NSColor does not, itself, implement _createCGColorWithAlpha: which is one of the reasons it's, in effect, an abstract class. As far as I can see, all the descendant classes do implement it - but please don't quote me! ®

Regcast training : Hyper-V 3.0, VM high availability and disaster recovery

Latest Comments

RE: Getting localized strings doesn't require private API

Thanks for that, Brian - interesting. Given the example code you provide, it's easy to see why the Apple developers came up with a more succinct way of doing things. ;-)

Incidentally, _NXKitString is just a wrapper around [NSBundle localizedStringForKey:value:table:], using another undocumented routine to retrieve the bundle instance for AppKit.

Dave

0
0

Getting localized strings doesn't require private API

You can get the same thing by using the NSLocalizedStringFromTableInBundle() function, declared in Foundation. Following the example in the post, the call would look like:

NSString* cancelString = NSLocalizedStringFromTableInBundle(@"Cancel", @"Preferences", [NSBundle bundleWithIdentifier:@"com.apple.AppKit"], nil);

You can also do this same trick with any framework that has .strings resources like AppKit has, just substitute the identifier of the framework for @"com.apple.AppKit".

0
0
Anonymous Coward

@Paul Maddox

Erm, it's not a blog post it's a development article posted in the Software->Developer section.

0
0

More from The Register

SCO vs. IBM battle resumes over ownership of Unix
Zombie lawsuit back and wants to suck the brains out of Linux
Bjarne Again: Hallelujah for C++
Plus: Now officially OK to admit you never used STL algorithms
Interwebs taunt Sir Jony over Apple eye candy makeover
Hey Ive, Ive... add more unicorns, willya?
Apple: iOS7 dayglo Barbie makeover is UNFINISHED - report
Plus: You don't like the icons? Blame marketing
Red Hat to ditch MySQL for MariaDB in RHEL 7
So long, Oracle! Don't let the door hit you on the way out
Shy? Socially inadequate? Fiddling with your phone could help
App 'tells the brutal truth' about social inadequates' chatup lines
Java EE 7 melds HTML5 with enterprise apps
New release arrives with GlassFish, NetBeans support
 breaking news
'Office Facebook' firm Tibbr wants you to PAY for mobe-meetings app
Great idea. Punters won't cough for it though
 breaking news
The only Waze is Google: Ad giant tipped to gobble map app 'for $1.3bn'
Pac-Man-satnav-ish upstart in bidding war with Apple, Facebook
 breaking news
PM Cameron calls for modern, programmable computers! (We think)
IT education musings to G8 chiefs to mystify IT industry