1

I can reproduce the following C#/.NET:

//A
String.Format("There are {0} cats in my {1} and no {2}", 2, "house", "dogs");

in Objective-C/Cocoa:

//B
[NSString stringWithFormat:@"There are %d cats in my %@ and no %@", 2, "house", "dogs"];

But I can't do this:

//C
String.Format("I have {0} dogs in my house.  My {0} dogs are very nice, but it is hard to walk {0} dogs at the same time.", numDogs);

in Objective-C:

//D
[NSString stringWithFormat:@"I have %d dogs in my house.  My %d dogs are very nice, but it is hard to walk %d dogs at the same time.", numDogs];

because it uses the printf format or whatever. Is there a more advanced way of doing this? Is there some way of doing KVC in string-parsing?

This is technically what I'd like:

[player setValue:@"Jimmy" forKey@"PlayerName"];
//later
[NSString stringWithMagicFormat:@"<PlayerName> sat on a bench in the middle of winter, and <GenderPronoun> felt very cold." andPlayer:player];
// or
[NSString stringwithMagicFormat: playerEnteredStringWithTagInIt andPlayer:player];

But I'll settle for:

String.Format(playerEnteredStringWithTagInIt, player.PlayerName, player.PlayerGender, player.GenderPronoun, ...);

Thanks,

Stephen Furlani
  • 6,794
  • 4
  • 31
  • 60

3 Answers3

5
[NSString stringWithFormat:@"I have %1$d dogs in my house.  My %1$d dogs are very nice, but it is hard to walk %1$d dogs at the same time.", numDogs];

(See man 3 printf.)

Wevah
  • 28,182
  • 7
  • 83
  • 72
  • +1 internets DUH. I'll accept this if no one posts anything about KVC. – Stephen Furlani Jan 20 '11 at 16:29
  • @Wevah, this isn't C99 compliant? ["The C99 standard does not include the style using '$', which comes from the Single Unix Specification."](http://linux.die.net/man/3/printf) – Stephen Furlani Jan 20 '11 at 16:30
  • Why does it need to be? Can you not use `gnu99`? If you're compiling for OS X (which I assume since you tagged it "cocoa"), the OS X man page doesn't have that caveat. – Wevah Jan 20 '11 at 16:34
  • (Dang, I wanted to remove the "why" as it sounded mad, but I can't anymore.) – Wevah Jan 20 '11 at 16:42
  • Nah. It's a valid question with no perceivable attitude. Your'e fine. :-) – Joshua Nozzi Jan 20 '11 at 18:02
  • Ugh, because I got [reemed out](http://stackoverflow.com/questions/4506870/using-macro-in-objective-c-to-log-function-name-and-line-number/4510392#4510392) on an answer that wasn't C99 compliant before. *I* don't care. It doesn't make a difference to me. – Stephen Furlani Jan 20 '11 at 18:08
  • 1
    While using syntax extensions in macros can be a problem for some users of the code, using formatting arguments for `[NSString stringWithFormat:]` that aren’t specified by C99 can’t be. I mean, `%@` isn’t in C99 either. – Jens Ayton Jan 20 '11 at 18:14
1

If you are using NSString and stringWithFormat:, then I don't think the C99 compliance really applies: while stringWithFormat: is similar to printf, it has its own implementation that has been around for a while.

The NSLocalizedString() macro actually does this for you automatically. For example, say I have the following code:

 @implementation MDAppController

- (id)init {
    if (self = [super init]) {
        NSArray *catsArray = [NSArray array];
        NSString *string = [NSString stringWithFormat:
         NSLocalizedString(@"There are %lu %@ %@ in my home.", @"no comment"),
        (unsigned long)[catsArray count],
                            NSLocalizedString(@"red", @""),
                            NSLocalizedString(@"cats", @"")];
        NSLog(@"string == %@", string);
    }
    return self;
}

@end

If I then run the following in Terminal

/usr/bin/genstrings ~/Developer/NSLocalizedString/MDAppController.m -o ~/Desktop/Strings

(Though I prefer just dragging the proxy icon in the window title bar onto this AppleScript droplet I keep in my Dock: GenerateStrings.app. It (over)writes the generated .strings files into ~/Desktop/Strings/ (creating it if necessary). You can edit the script by dropping it onto AppleScript Editor).

It will generate a UTF16 Localizable.strings file which you can add to your project. In it it will have the following:

English.lproj/Localizable.strings

/* No comment provided by engineer. */
"cats" = "cats";

/* No comment provided by engineer. */
"red" = "red";

/* no comment */
"There are %lu %@ %@ in my home." = "There are %1$lu %2$@ %3$@ in my home.";

Duplicate it to a Spanish language project sub folder and change the order of items as necessary (since in Spanish adjectives usually come after the noun):

Spanish.lproj/Localizable.strings

/* No comment provided by engineer. */
"cats" = "gatos";

/* No comment provided by engineer. */
"red" = "rojos";

/* no comment */
"There are %lu %@ %@ in my home." = "Está %1$lu %3$@ %2$@ en mi casa.";

When English is my primary (topmost) language in Language & Text pref pane:

1/20/2011 12:59:37 PM NSLocalizedString[1777] string == There are 0 red cats in my home.

When Spanish is my primary (topmost) language in Language & Text pref pane:

1/20/2011 12:37:02 PM NSLocalizedString[1702] string == Está 0 gatos rojos en mi casa.

The .strings files are in a sense key value pairs.

For more info see Resource Programming Guide: String Resources

NSGod
  • 22,699
  • 3
  • 58
  • 66
0

I like everyone's answers, but I just spent 15 minutes in the Docs and found the answer I was looking for... sigh, RTM! Here's my solution in case anyone else is looking for something similar.

// Put this in a category of NSString
- (NSString*) stringByReplacingOccurrencesOfKeysWithValues:(NSDictionary*)keyValuePairs
{
    NSString *fStr = self;
    if (keyValuePairs) {
        for (NSString *key in [keyValuePairs allKeys]) {
            NSString *value = [NSString stringWithFormat:@"%@",[keyValuePairs valueForKey:key]];
            fStr = [fStr stringByReplacingOccurrencesOfString:key withString:value];
        }
    }
    return fStr;
}

Seems to work fine, this was my test code:

// testing NSString+Keys
NSMutableDictionary *mud = [NSMutableDictionary dictionaryWithCapacity:1];
[mud setValue:@"Jimmy" forKey:@"{PlayerName}"];
[mud setValue:[NSNumber numberWithInt:97] forKey:@"{HitPoints}"];
[mud setValue:[NSNumber numberWithFloat:1.0678f] forKey:@"{meters}"];
NSString *mystr = [@"Help me {PlayerName} before it is too late! I only have {HitPoints}hp left!  And I am {meters}m away! {PlayerName} hurry!" stringByReplacingOccurrencesOfKeysWithValues:mud];
NSLog(mystr);

Output was:

Help me Jimmy before it is too late! I only have 97hp left! And I am 1.0678m away! Jimmy hurry!

Stephen Furlani
  • 6,794
  • 4
  • 31
  • 60