Ways to build NSStrings in Objective-C? [duplicate] - ios

C# has syntax that allows you to specify the argument index in a string format specifier, e.g.:
string message = string.Format("Hello, {0}. You are {1} years old. How does it feel to be {1}?", name, age);
You can use arguments more than once and also omit arguments that are provided from being used. Another question mentions the same formatting for C/C++ in the form of %[index]$[format], e.g. %1$i. I have not been able to get NSString to fully respect this syntax, because it does behave well when omitting arguments from the format. The following does not work as expected (EXC_BAD_ACCESS because it attempts to dereference the age parameter as a NSObject*):
int age = 23;
NSString * name = #"Joe";
NSString * message = [NSString stringWithFormat:#"Age: %2$i", name, age];
The positional arguments are respected only if there are no missing arguments from the format (which seems to be an odd requirement):
NSString * message = [NSString stringWithFormat:#"Age: %2$i; Name: %1$#", name, age];
All these calls work properly in OS X:
printf("Age: %2$i", [name UTF8String], age);
printf("Age: %2$i %1$s", [name UTF8String], age);
Is there a way of accomplishing this using NSString in Objective-C / Cocoa? It would be useful for localization purposes.

NSString and CFString support reorderable/positional arguments.
NSString *string = [NSString stringWithFormat: #"Second arg: %2$#, First arg %1$#", #"<1111>", #"<22222>"];
NSLog(#"String = %#", string);
Also, see the documentation at Apple: String Resources

The following code fixes the bug specified in this issue. It is a workaround and renumbers the placeholders to fill gaps.
+ (id)stringWithFormat:(NSString *)format arguments:(NSArray*) arguments
{
NSMutableArray *filteredArguments = [[NSMutableArray alloc] initWithCapacity:arguments.count];
NSMutableString *correctedFormat = [[NSMutableString alloc ] initWithString:format];
NSString *placeHolderFormat = #"%%%d$";
int actualPlaceholderIndex = 1;
for (int i = 1; i <= arguments.count; ++i) {
NSString *placeHolder = [[NSString alloc] initWithFormat:placeHolderFormat, i];
if ([format rangeOfString:placeHolder].location != NSNotFound) {
[filteredArguments addObject:[arguments objectAtIndex:i - 1]];
if (actualPlaceholderIndex != i) {
NSString *replacementPlaceHolder = [[NSString alloc] initWithFormat:placeHolderFormat, actualPlaceholderIndex];
[correctedFormat replaceAllOccurrencesOfString:placeHolder withString:replacementPlaceHolder];
[replacementPlaceHolder release];
}
actualPlaceholderIndex++;
}
[placeHolder release];
}
if (filteredArguments.count == 0) {
//No numbered arguments found: just copy the original arguments. Mixing of unnumbered and numbered arguments is not supported.
[filteredArguments setArray:arguments];
}
NSString* result;
if (filteredArguments.count == 0) {
//Still no arguments: don't use initWithFormat in this case because it will crash: just return the format string
result = [NSString stringWithString:format];
} else {
char *argList = (char *)malloc(sizeof(NSString *) * [filteredArguments count]);
[filteredArguments getObjects:(id *)argList];
result = [[[NSString alloc] initWithFormat:correctedFormat arguments:argList] autorelease];
free(argList);
}
[filteredArguments release];
[correctedFormat release];
return result;
}

After doing more research, it appears Cocoa respects positional syntax in printf. Therefore an alternate pattern would be:
char msg[512] = {0};
NSString * format = #"Age %2$i, Name: %1$s"; // loaded from resource in practice
sprintf(msg, [format UTF8String], [name UTF8String], age);
NSString * message = [NSString stringWithCString:msg encoding:NSUTF8StringEncoding];
However, it would be nice if there was an implementation of this on NSString.

Related

Objective C - How to compare string from the console

My code always returns "fail" for the following string comparison using isEqualToString. What is the correct way to compare strings coming from the console?
char buf[MAX_LENGTH];
fgets(buf, MAX_LENGTH, stdin);
NSString *s = [[NSString alloc] initWithUTF8String:buf];
NSLog(#"You typed %#", s);
NSString *n = #"exit";
if ([n isEqualToString:s]) {
NSLog(#"success!");
} else {
NSLog(#"fail");
}
The result of fgets contains "\n", So you need define your "n" as this:
NSString *n = #"exit\n";
Or remove the "\n" from "s":
NSString *s = [[NSString alloc] initWithUTF8String:buf];
s = [s stringByReplacingOccurrencesOfString:#"\n" withString:#""];

Objective C - #define using __VA_ARGS__

I am learning how to use macro but now confused with one.
I am trying to create a NSString concatenate which will just append every params to each other.
for example : concatOP(#"hey",#"Jude",#"Don't") would return a NSString containing : #"heyJudeDon't"
I actually made a bit of code (some found here as well) which get the number of params but I don't succeed to make the second part of the job.
#define NUMARGS(...) ( sizeof((int[]){__VA_ARGS__}) / sizeof(int) )
#define concatOP(...) NSMutableString *format = [[NSMutableString alloc] init];\
for( int i = 0; i < NUMARGS(__VA_ARGS__); i++){\
[format appendString:#"%#"];}\
[[NSString alloc] initWithFormat:format, __VA_ARGS__]
I actually get many errors, telling me that format doesn't exist or that I miss some ";" or other ending tags.
Here is yours macro:
#define concatOP(...) [#[__VA_ARGS__] componentsJoinedByString:#""]
EDIT:
if you unwind yours macro NSString* result = concatOP(#"hey",#"Jude",#"Don't");
you will get:
NSString* result = NSMutableString *format = [[NSMutableString alloc] init]; for( int i = 0; i < NUMARGS(#"hey",#"Jude",#"Don't"); i++){ format = [format appendString:#"%#"];} [[NSString alloc] initWithFormat:format, #"hey",#"Jude",#"Don't"];
Looks odd.
This doesn't exactly answer your question, but NSString literals are concatenated by the compiler, just like their C-counterparts, so this code works out of the box:
NSString *str = #"Hey" #"Jude" #"Don't";
which is the same as:
NSString *str = #"HeyJudeDon't";
This is typically used to split a long string literal across multiple lines of the source file.
Bottom line; you don't need all those messy macros and pointless methods to do this.
I don't know how to do this with macros.
You can do it in Objective C like:
Implement a method like:
- (NSString *)concateStrings:(NSString *)firstArg, ...
{
NSMutableString *concatString = [[NSMutableString alloc] init];
va_list args;
va_start(args, firstArg);
for (NSString *arg = firstArg; arg != nil; arg = va_arg(args, NSString*))
{
[concatString appendString:arg];
}
va_end(args);
return concatString;
}
You can call this method like:
NSLog(#"%#",[self concateStrings:#"hey",#"Jude",#"Don't",nil]) ;
Output:
heyJudeDon't
Make sure that you pass nil at the end.

UITableView Headers with UTF-8 Strings?

I'm formatting my UITableView headers with Unix %s formatting because the Objective C format code %# doesn't handle padding (e.g. %20#%20s). Unfortunately, if I have accented foreign characters, they aren't displayed properly. The word "voilà" appears like this:
Here's how I generate my table header:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
...
//NSString *tblHdr = [NSString stringWithFormat:#"%12s", #"voilà"];
NSString *tblHdr = [NSString stringWithFormat:#"%12s", [#"voilà" UTF8String]]; // Fixed example
...
return tblHdr;
}
[Update]
Here's the actual code from my project. I tried to keep it simple in my original example:
tblHdr = [NSString stringWithFormat:#"%-45s%12s%12s%35s",
[NSLocalizedString(#"Quiz Name", #"") UTF8String],
[NSLocalizedString(#"Correct", #"") UTF8String],
[NSLocalizedString(#"Missed", #"") UTF8String],
[NSLocalizedString(#"Score", #"") UTF8String]
];
It seems that the %s format expects a C string in the system encoding,
so this works
NSStringEncoding enc = CFStringConvertEncodingToNSStringEncoding(CFStringGetSystemEncoding());
NSString *s = [NSString stringWithFormat:#"%12s", [#"voilá" cStringUsingEncoding:enc]];
(as long as the string can be represented in the system encoding).
Otherwise you can pad the string manually:
NSString *t = #"voilá";
if ([t length] < 12) {
t = [[#"" stringByPaddingToLength:(12 - [t length]) withString:#" " startingAtIndex:0] stringByAppendingString:t];
}
(or use #Daij-Djan's method, which was posted while I wrote this).
issue: %s formats a char* which is != NSString. so:
NSString *tblHdr = [NSString stringWithFormat:#"%12s", #"voilà".UTF8String];
This doesn't work as %s seems to not do multibyte right IMO!? Not sure
I'd stick with NSStrings... just add the spaces manually
#import <Foundation/Foundation.h>
int main(int argc, char *argv[]) {
#autoreleasepool {
//wrong
NSString *tblHdr = [NSString stringWithFormat:#"%12s", #"voilà".UTF8String];
NSLog(#"%#", tblHdr);
//ok
NSMutableString *rawTblHdr = #"voilà".mutableCopy;
while(12-rawTblHdr.length) {
[rawTblHdr insertString:#" " atIndex:0];
}
NSLog(#"%#", rawTblHdr);
}
}

Create string based on characters expected

I would like to create a string based on the number of characters passed in. Each character passed in will be a "X". So for example, if the length passed in is 5, then the string created should be
NSString *testString=#"XXXXX";
if it is 2 then it would be
NSString *testString=#"XX";
Can anyone tell me what the most efficient way to do this would be?
Thank you!
If you know the maximum length is some reasonable number then you could do something simple like this:
- (NSString *)xString:(NSUInteger)length {
static NSString *xs = #"XXXXXXXXXXXXXXXXXXXXXXXXXXX";
return [xs substringToIndex:length];
}
NSString *str = [self xString:5]; // str will be #"XXXXX";
If you pass in too large of a length, the app will crash - add more Xs to xs.
This approach is more efficient than building up an NSMutableString but it does make an assumption about the maximum length you might need.
- (NSString *)stringOf:(NSString *)str times:(NSInteger)count
{
NSMutableString *targ = [[NSMutableString alloc] initWithCapacity:count];
for (int i=0; i < count; i++)
{
[targ appendString:str];
}
return targ;
}
and
[self stringOf:#"X" times:4];
note that initWithCapacity: (in performance manner) better than init. But I guess that's all for efficiency.
The way I would do it is
NSMutableString *xString = [[NSMutableString alloc] init];
while ( int i = 0; i < testString.length; i++ ) {
[xString appendString:#"X"];
i++;
}
NSUInteger aLength. // assume this is the argument
NSMutableString *xStr = [NSMutableString stringWithCapacity: aLength];
for ( NSUInteger i = 0; i < aLength; i++ ) {
[xStr appendFormat:#"X"];
}
The following will do what you ask in one call:
NSString *result = [#"" stringByPaddingToLength:numberOfCharsWanted
withString:characterToRepeat
startingAtIndex:0];
where numberOfCharsWanted is an NSUInteger and characterToRepeat is an NSString containing the character.

Decoding the scanned barcode value to int value

When I scan the barcode and I get some value if it is Equal=2 then I need to display with == and if it is Equal=3 then I need to display with = and if the value is 4 then invalid.
But Scanned Barcode are of integer value -- when decode using NSASCII it is displaying only till value 127 after that it is showing invalid results. Eg: if my Barcode value = 9699 the result value=jem then my added result value=jem= actualstring value=%åasc value id only showing 37
Here is my code:
- (void) readerView:(ZBarReaderView *)view didReadSymbols:(ZBarSymbolSet *)syms fromImage:(UIImage *)img
{
// do something useful with results -- cool thing is that you get access to the image too
for (ZBarSymbol *symbol in syms) {
[resultsBox setText:symbol.data];
if ([resultsBox.text length] == 2) {
addedresult.text = [resultsBox.text stringByAppendingString:#"=="];
} else if ([resultsBox.text length] == 3) {
addedresult.text = [resultsBox.text stringByAppendingString:#"="];
} if ([resultsBox.text length] >= 4) {
addedresult.text = #"Invalid";
}
[Base64 initialize];
NSString *myString = [[NSString alloc]initWithString:addedresult.text];
NSData * data = [Base64 decode:myString];
NSString * actualString = [[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding];
NSLog(#"%#",actualString);
labeltext.text= actualString;
int asc = [actualString characterAtIndex:0];
label.text = [NSString stringWithFormat:#"%d", asc];
[actualString release];
break;
}
}
Since someone revived this question's comments, i'll revive this entire post.
You shouldn't go through NSData to create an NSString from something you already have, and you're probably losing something along the way. Go directly to NSString using stringWithFormat. Also, ASCII will come back and byte you later, if you have a choice, use UTF8.
NSString *actualStringUTF8 = [NSString stringWithFormat:#"%#",[addedresult.text urlEncodeUsingEncoding:NSUTF8StringEncoding]];
NSString *actualStringASCII = [NSString stringWithFormat:#"%#",[addedresult.text urlEncodeUsingEncoding:NSUTF8StringEncoding]];
NSLog(#"%#",actualStringUTF8);
NSLog(#"%c",[actualStringUTF8 UTF8String]); //This is a const char*
Secondly, I looked into the SDK and it says symbol.data is already an NSString*. Depending on what you want, you may not need to do anything. If you do end up needing to change encoding, make sure you understand why you need to (one good reason is "the rest of the application uses NS****StringEncoding").
Also make sure you compare strings the correct "Objective-C" way:
[actualString isEqualToString: testString];
NOT actualString == testString;

Resources