How to create a NSString from a format string like #"xxx=%#, yyy=%#" and a NSArray of objects? - objective-c

Is there any way to create a new
NSString from a format string like #"xxx=%#, yyy=%#" and a NSArray of objects?
In the NSSTring class there are many methods like:
- (id)initWithFormat:(NSString *)format arguments:(va_list)argList
- (id)initWithFormat:(NSString *)format locale:(id)locale arguments:(va_list)argList
+ (id)stringWithFormat:(NSString *)format, ...
but non of them takes a NSArray as an argument, and I cannot find a way to create a va_list from a NSArray...

It is actually not hard to create a va_list from an NSArray. See Matt Gallagher's excellent article on the subject.
Here is an NSString category to do what you want:
#interface NSString (NSArrayFormatExtension)
+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;
#end
#implementation NSString (NSArrayFormatExtension)
+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments
{
char *argList = (char *)malloc(sizeof(NSString *) * arguments.count);
[arguments getObjects:(id *)argList];
NSString* result = [[[NSString alloc] initWithFormat:format arguments:argList] autorelease];
free(argList);
return result;
}
#end
Then:
NSString* s = [NSString stringWithFormat:#"xxx=%#, yyy=%#" array:#[#"XXX", #"YYY"]];
NSLog( #"%#", s );
Unfortunately, for 64-bit, the va_list format has changed, so the above code no longer works. And probably should not be used anyway given it depends on the format that is clearly subject to change. Given there is no really robust way to create a va_list, a better solution is to simply limit the number of arguments to a reasonable maximum (say 10) and then call stringWithFormat with the first 10 arguments, something like this:
+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments
{
if ( arguments.count > 10 ) {
#throw [NSException exceptionWithName:NSRangeException reason:#"Maximum of 10 arguments allowed" userInfo:#{#"collection": arguments}];
}
NSArray* a = [arguments arrayByAddingObjectsFromArray:#[#"X",#"X",#"X",#"X",#"X",#"X",#"X",#"X",#"X",#"X"]];
return [NSString stringWithFormat:format, a[0], a[1], a[2], a[3], a[4], a[5], a[6], a[7], a[8], a[9] ];
}

Based on this answer using Automatic Reference Counting (ARC): https://stackoverflow.com/a/8217755/881197
Add a category to NSString with the following method:
+ (id)stringWithFormat:(NSString *)format array:(NSArray *)arguments
{
NSRange range = NSMakeRange(0, [arguments count]);
NSMutableData *data = [NSMutableData dataWithLength:sizeof(id) * [arguments count]];
[arguments getObjects:(__unsafe_unretained id *)data.mutableBytes range:range];
NSString *result = [[NSString alloc] initWithFormat:format arguments:data.mutableBytes];
return result;
}

One solution that came to my mind is that I could create a method that works with a fixed large number of arguments like:
+ (NSString *) stringWithFormat: (NSString *) format arguments: (NSArray *) arguments {
return [NSString stringWithFormat: format ,
(arguments.count>0) ? [arguments objectAtIndex: 0]: nil,
(arguments.count>1) ? [arguments objectAtIndex: 1]: nil,
(arguments.count>2) ? [arguments objectAtIndex: 2]: nil,
...
(arguments.count>20) ? [arguments objectAtIndex: 20]: nil];
}
I could also add a check to see if the format string has more than 21 '%' characters and throw an exception in that case.

#Chuck is correct about the fact that you can't convert an NSArray into varargs. However, I don't recommend searching for the pattern %# in the string and replacing it each time. (Replacing characters in the middle of a string is generally quite inefficient, and not a good idea if you can accomplish the same thing in a different way.) Here is a more efficient way to create a string with the format you're describing:
NSArray *array = ...
NSAutoreleasePool *pool = [NSAutoreleasePool new];
NSMutableArray *newArray = [NSMutableArray arrayWithCapacity:[array count]];
for (id object in array) {
[newArray addObject:[NSString stringWithFormat:#"x=%#", [object description]]];
}
NSString *composedString = [[newArray componentsJoinedByString:#", "] retain];
[pool drain];
I included the autorelease pool for good housekeeping, since an autoreleased string will be created for each array entry, and the mutable array is autoreleased as well. You could easily make this into a method/function and return composedString without retaining it, and handle the autorelease elsewhere in the code if desired.

This answer is buggy. As noted, there is no solution to this problem that is guaranteed to work when new platforms are introduced other than using the "10 element array" method.
The answer by solidsun was working well, until I went to compile with 64-bit architecture. This caused an error:
EXC_BAD_ADDRESS type EXC_I386_GPFLT
The solution was to use a slightly different approach for passing the argument list to the method:
+ (id)stringWithFormat:(NSString *)format array:(NSArray*) arguments;
{
__unsafe_unretained id * argList = (__unsafe_unretained id *) calloc(1UL, sizeof(id) * arguments.count);
for (NSInteger i = 0; i < arguments.count; i++) {
argList[i] = arguments[i];
}
NSString* result = [[NSString alloc] initWithFormat:format, *argList] ;// arguments:(void *) argList];
free (argList);
return result;
}
This only works for arrays with a single element

There is no general way to pass an array to a function or method that uses varargs. In this particular case, however, you could fake it by using something like:
for (NSString *currentReplacement in array)
[string stringByReplacingCharactersInRange:[string rangeOfString:#"%#"]
withString:currentReplacement];
EDIT: The accepted answer claims there is a way to do this, but regardless of how fragile this answer might seem, that approach is far more fragile. It relies on implementation-defined behavior (specifically, the structure of a va_list) that is not guaranteed to remain the same. I maintain that my answer is correct and my proposed solution is less fragile since it only relies on defined features of the language and frameworks.

For those who need a Swift solution, here is an extension to do this in Swift
extension String {
static func stringWithFormat(format: String, argumentsArray: Array<AnyObject>) -> String {
let arguments = argumentsArray.map { $0 as! CVarArgType }
let result = String(format:format, arguments:arguments)
return result
}
}

Yes, it is possible. In GCC targeting Mac OS X, at least, va_list is simply a C array, so you'll make one of ids, then tell the NSArray to fill it:
NSArray *argsArray = [[NSProcessInfo processInfo] arguments];
va_list args = malloc(sizeof(id) * [argsArray count]);
NSAssert1(args != nil, #"Couldn't allocate array for %u arguments", [argsArray count]);
[argsArray getObjects:(id *)args];
//Example: NSLogv is the version of NSLog that takes a va_list instead of separate arguments.
NSString *formatSpecifier = #"\n%#";
NSString *format = [#"Arguments:" stringByAppendingString:[formatSpecifier stringByPaddingToLength:[argsArray count] * 3U withString:formatSpecifier startingAtIndex:0U]];
NSLogv(format, args);
free(args);
You shouldn't rely on this nature in code that should be portable. iPhone developers, this is one thing you should definitely test on the device.

- (NSString *)stringWithFormat:(NSString *)format andArguments:(NSArray *)arguments {
NSMutableString *result = [NSMutableString new];
NSArray *components = format ? [format componentsSeparatedByString:#"%#"] : #[#""];
NSUInteger argumentsCount = [arguments count];
NSUInteger componentsCount = [components count] - 1;
NSUInteger iterationCount = argumentsCount < componentsCount ? argumentsCount : componentsCount;
for (NSUInteger i = 0; i < iterationCount; i++) {
[result appendFormat:#"%#%#", components[i], arguments[i]];
}
[result appendString:[components lastObject]];
return iterationCount == 0 ? [result stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]] : result;
}
Tested with format and arguments:
NSString *format = #"xxx=%#, yyy=%# last component";
NSArray *arguments = #[#"XXX", #"YYY", #"ZZZ"];
Result: xxx=XXX, yyy=YYY last component
NSString *format = #"xxx=%#, yyy=%# last component";
NSArray *arguments = #[#"XXX", #"YYY"];
Result: xxx=XXX, yyy=YYY last component
NSString *format = #"xxx=%#, yyy=%# last component";
NSArray *arguments = #[#"XXX"];
Result: xxx=XXX last component
NSString *format = #"xxx=%#, yyy=%# last component";
NSArray *arguments = #[];
Result: last component
NSString *format = #"some text";
NSArray *arguments = #[#"XXX", #"YYY", #"ZZZ"];
Result: some text

I found some code on the web that claims that this is possible however I haven't managed to do it myself, however if you don't know the number of arguments in advance you also need to build the format string dynamically so I just don't see the point.
You better off just building the string by iterating the array.
You might find the stringByAppendingString: or stringByAppendingFormat: instance method handy .

One can create a category for NSString and make a function which receives a format, an array and returns the string with replaced objects.
#interface NSString (NSArrayFormat)
+ (NSString *)stringWithFormat:(NSString *)format arrayArguments:(NSArray *)arrayArguments;
#end
#implementation NSString (NSArrayFormat)
+ (NSString *)stringWithFormat:(NSString *)format arrayArguments:(NSArray *)arrayArguments {
static NSString *objectSpecifier = #"%#"; // static is redundant because compiler will optimize this string to have same address
NSMutableString *string = [[NSMutableString alloc] init]; // here we'll create the string
NSRange searchRange = NSMakeRange(0, [format length]);
NSRange rangeOfPlaceholder = NSMakeRange(NSNotFound, 0); // variables are declared here because they're needed for NSAsserts
NSUInteger index;
for (index = 0; index < [arrayArguments count]; ++index) {
rangeOfPlaceholder = [format rangeOfString:objectSpecifier options:0 range:searchRange]; // find next object specifier
if (rangeOfPlaceholder.location != NSNotFound) { // if we found one
NSRange substringRange = NSMakeRange(searchRange.location, rangeOfPlaceholder.location - searchRange.location);
NSString *formatSubstring = [format substringWithRange:substringRange];
[string appendString:formatSubstring]; // copy the format from previous specifier up to this one
NSObject *object = [arrayArguments objectAtIndex:index];
NSString *objectDescription = [object description]; // convert object into string
[string appendString:objectDescription];
searchRange.location = rangeOfPlaceholder.location + [objectSpecifier length]; // update the search range in order to minimize search
searchRange.length = [format length] - searchRange.location;
} else {
break;
}
}
if (rangeOfPlaceholder.location != NSNotFound) { // we need to check if format still specifiers
rangeOfPlaceholder = [format rangeOfString:#"%#" options:0 range:searchRange];
}
NSAssert(rangeOfPlaceholder.location == NSNotFound, #"arrayArguments doesn't have enough objects to fill specified format");
NSAssert(index == [arrayArguments count], #"Objects starting with index %lu from arrayArguments have been ignored because there aren't enough object specifiers!", index);
return string;
}
#end
Because NSArray is created at runtime we cannot provide compile-time warnings, but we can use NSAssert to tell us if number of specifiers is equal with number of objects within array.
Created a project on Github where this category can be found. Also added Chuck's version by using 'stringByReplacingCharactersInRange:' plus some tests.
Using one million objects into array, version with 'stringByReplacingCharactersInRange:' doesn't scale very well (waited about 2 minutes then closed the app). Using the version with NSMutableString, function made the string in about 4 seconds. The tests were made using simulator. Before usage, tests should be done on a real device (use a device with lowest specs).
Edit: On iPhone 5s the version with NSMutableString takes 10.471655s (one million objects); on iPhone 5 takes 21.304876s.

Here's the answer without explicitly creating an array:
NSString *formattedString = [NSString stringWithFormat:#"%# World, Nice %#", #"Hello", #"Day"];
First String is the target string to be formatted, the next string are the string to be inserted in the target.

No, you won't be able to. Variable argument calls are solved at compile time, and your NSArray has contents only at runtime.

Related

Take all numbers separated by spaces from a string and place in an array

I have a NSString formatted like this:
"Hello world 12 looking for some 56"
I want to find all instances of numbers separated by whitespace and place them in an NSArray. I dont want to remove the numbers though.
Whats the best way of achieving this?
This is a solution using regular expression as suggested in the comment.
NSString *string = #"Hello world 12 looking for some 56";
NSRegularExpression *expression = [NSRegularExpression regularExpressionWithPattern:#"\\b\\d+" options:nil error:nil];
NSArray *matches = [expression matchesInString:string options:nil range:(NSMakeRange(0, string.length))];
NSMutableArray *result = [[NSMutableArray alloc] init];
for (NSTextCheckingResult *match in matches) {
[result addObject:[string substringWithRange:match.range]];
}
NSLog(#"%#", result);
First make an array using NSString's componentsSeparatedByString method and take reference to this SO question. Then iterate the array and refer to this SO question to check if an array element is number: Checking if NSString is Integer.
I don't know where you are looking to do perform this action because it may not be fast (such as if it's being called in a table cell it may be choppy) based upon the string size.
Code:
+ (NSArray *)getNumbersFromString:(NSString *)str {
NSMutableArray *retVal = [NSMutableArray array];
NSCharacterSet *numericSet = [NSCharacterSet decimalDigitCharacterSet];
NSString *placeholder = #"";
unichar currentChar;
for (int i = [str length] - 1; i >= 0; i--) {
currentChar = [str characterAtIndex:i];
if ([numericSet characterIsMember:currentChar]) {
placeholder = [placeholder stringByAppendingString:
[NSString stringWithCharacters:&currentChar
length:[placeholder length]+1];
} else {
if ([placeholder length] > 0) [retVal addObject:[placeholder intValue]];
else placeholder = #"";
return [retVal copy];
}
To explain what is happening above, essentially I am,
going through every character until I find a number
adding that number including any numbers after to a string
once it finds a number it adds it to an array
Hope this helps please ask for clarification if needed

Objective-C, making an NSArray of each unique character in an NSString

I've searched around and found several questions answered which were similar to this one, but not exactly the same and I have not been able to get the sample code given in those answers to work for me. I admit this could very likely be my ignorance of Objective-C getting in the way. My situation is this:
I have an NSString from a text file which contains a variety of characters. The length of the string can vary based on what is in the text file. I need to make an array giving each individual character in the string.
I've tried 5 different approaches to the problem (three of them from answers on this site) but each effort I've made to do this has resulted in a) segmentation faults I couldn't track down, b) the array remaining NULL while giving compiler warnings, or c) the array remaining NULL without compiler warnings. In case it matter's, I'm using: gcc -framework Foundation -std=c99 TestCode.m -o TestProgram
Sorry there's no specific code here because I've deleted all my failed efforts in frustration. I guess there's a reason why you shouldn't try to learn a programming language at the same time as trying to learn a new subject that you are applying the language to :)
Would anyone be so helpful as to give me a couple of snippets to work with here?
This is how you would do it.
Read the contents of the file into a NSSString
Enumerate all all the characters
Add them to a NSMutableSet
Get allObjects from the set
In code that roughly translates to this
// 1. Get the contents of the file
NSError *error = NULL;
NSString *textFromFile = [[NSString alloc] initWithContentsOfFile:pathToFile
encoding:NSUTF8StringEncoding
error:&error];
if (!textFromFile) {
// handle error
}
// 2. Enumerate all the characters
// (I'm enumerating composed characters to be able to support for example Chinese)
NSMutableSet *characters = [NSMutableSet set];
[textFromFile enumerateSubstringsInRange:NSMakeRange(0, textFromFile.length)
options:NSStringEnumerationByComposedCharacterSequences
usingBlock:^(NSString *substring, NSRange substringRange, NSRange enclosingRange, BOOL *stop) {
// 3. Add them to a mutable set
[characters addObject:substring];
}];
// 4. Get all the objects from the set (note that it's not sorted)
NSArray *allCharacters = [characters allObjects];
Here's an NSString category that will make an NSArray of all characters in a string:
- (NSArray*)charactersAsArray
{
NSMutableArray* array = [#[] mutableCopy];
for (int i = 0; i < self.length; i++) {
NSRange composedCharRange = [self rangeOfComposedCharacterSequenceAtIndex:i];
NSString* character = [self substringWithRange:composedCharRange];
if (character) {
[array addObject:character];
}
}
return array;
}
Or for unique characters you can use:
- (NSArray*)uniqueCharactersAsArray
{
NSMutableArray* array = [#[] mutableCopy];
for (int i = 0; i < self.length; i++) {
NSRange composedCharRange = [self rangeOfComposedCharacterSequenceAtIndex:i];
NSString* character = [self substringWithRange:composedCharRange];
if (character && ![array containsObject:character]) {
[array addObject:character];
}
}
return array;
}
You can use it like this...
NSString* myString = #"disdiefgdsaéYsué8d9ieo";
NSArray* allCharactersArray = [myString charactersAsArray];
NSArray* uniqueCharactersArray = [myString uniqueCharactersAsArray];

splitting nsstring and putting into tableview

I am trying to split the string into parts and insert into a table how should i do it?
I got an error for splitting of the array which is: -[__NSArrayI componentsSeparatedByString:]: unrecognized selector sent to instance 0x7a421e0
NSArray *BusRoute = alightDesc;
int i;
int count = [BusRoute count];
for (i = 0; i < count; i++)
{
NSDictionary *dic = [BusRoute objectAtIndex: i];
NSDictionary *STEPS = [dic valueForKey:#"STEPS"];
NSString *AlightDesc = [STEPS valueForKey:#"AlightDesc"];
NSLog(#"AlightDesc = %#", AlightDesc);
NSArray *aDescArray = [AlightDesc componentsSeparatedByString:#","];
NSLog(#"aDescArray = %#", aDescArray);
}
This is the string which I'm splitting, i got it from the NSLog
AlightDesc = (
"Block1",
"Block2",
"Block3"
)
please help I'm stuck thanks.
Objective C is not a strongly typed language. All you know for sure about [STEPS valueForKey:#"AlightDesc"] is that it will return an object (of type id). When you wrote NSString *AlightDesc = [STEPS valueForKey:#"AlightDesc"] the compiler did not complain because NSString * is a valid object type. Unfortunately there is a logic error in your code so that what was actually stored under the key #"AlightDesc" is an NSArray. As others have mentioned, NSArray does not respond to componentsSeparatedByString: so you get an error at runtime.
The easy fix for this is to correct your logic: Either store an NSString in the first place or treat what you get out as an NSArray. As #janusfidel mentioned you can use an NSArray perfectly well in a table by using objectAtIndex: to get the string for the entry you want.
In some more complicated cases you may not know what you will be getting out of a dictionary for a particular key. In that case in Objective C you can just ask the object:
id anObject = [STEPS valueForKey:#"AlightDesc"];
if ([anObject isKindOfClass:[NSString class]]) {
NSString *aString = (NSString *)anObject;
// Treat as a string ...
} else if ([anObject isKindOfClass:[NSString class]]) {
// Object is an array ...
Your NSString *AlightDesc should look like this
NSString *AlightDesc = "Block1,Block2,Block3";
NSArray *aDescArray = [AlightDesc componentsSeparatedByString:#","];
If your string is what you say it is
AlightDesc = ("Block1","Block2","Block3");
then your string is the problem because it's already broken up.

Objective-C Category Question

I've created a custom sorting by creating a new category for the NSString class. Below is my code.
#implementation NSString (Support)
- (NSComparisonResult)sortByPoint:(NSString *)otherString {
int first = [self calculateWordValue:self];
int second = [self calculateWordValue:otherString];
if (first > second) {
return NSOrderedAscending;
}
else if (first < second) {
return NSOrderedDescending;
}
return NSOrderedSame;
}
- (int)calculateWordValue:(NSString *)word {
int totalValue = 0;
NSString *pointPath = [[NSBundle mainBundle] pathForResource:#"pointvalues"ofType:#"plist"];
NSDictionary *pointDictionary = [[NSDictionary alloc] initWithContentsOfFile:pointPath];
for (int index = 0; index < [word length]; index++) {
char currentChar = [word characterAtIndex:index];
NSString *individual = [[NSString alloc] initWithFormat:#"%c",currentChar];
individual = [individual uppercaseString];
NSArray *numbersForKey = [pointDictionary objectForKey:individual];
NSNumber *num = [numbersForKey objectAtIndex:0];
totalValue += [num intValue];
// cleanup
individual = nil;
numbersForKey = nil;
num = nil;
}
return totalValue;
}
#end
My question is whether I create a point dictionary to determine the point value associated with each character in the alphabet based on a plist. Then in my view controller, I call
NSArray *sorted = [words sortedArrayUsingSelector:#selector(sortByPoint:)];
to sort my table of words by their point values. However, creating a new dictionary each time the -sortByPoint: method is called is extremely inefficient. Is there a way to create the pointDictionary beforehand and use it for each subsequent call in the -calculateWordValue:?
This is a job for the static keyword. If you do this:
static NSDictionary *pointDictionary = nil
if (pointDictionary==nil) {
NSString *pointPath = [[NSBundle mainBundle] pathForResource:#"pointvalues" ofType:#"plist"];
pointDictionary = [[NSDictionary alloc] initWithContentsOfFile:pointPath];
}
pointDictionary will be persistent for the lifetime of your app.
One other optimization is to build a cache of scores by using this against each of your words:
[dict setObject:[NSNumber numberWithInt:[word calculateWordValue:word]] forKey:word];
Then use the keysSortedByValueUsingSelector: method to extract your list of words (note the selector chould be compare:, since the objects being compared are the NSNumbers).
Finally, the word argument on your method is redundant. Use self instead:
-(int)calculateWordValue {
...
for (int index = 0; index < [self length]; index++)
{
char currentChar = [self characterAtIndex:index];
...
}
...
}
Change your sortByPoint:(NSString *) otherString method to take the dictionary as a parameter, and pass it your pre-created dictionary.
sortByPoint:(NSString *)otherString withDictionary:(NSDictionary *)pointDictionary
EDIT: Won't work because of usage in sortedArrayWithSelector. Apologies. Instead, you may be better off creating a wrapper class for your point dictionary as a singleton which you then obtain a reference to each time your sort function runs.
In calculateWordValue:
NSDictionary *pointDictionary = [[DictWrapper sharedInstance] dictionary];
DictWrapper has an NSDictionary as a property, and a class method sharedInstance (to return the singleton. You have to set that dictionary and pre-initialize it before you do you first sorting.

Shortcuts in Objective-C to concatenate NSStrings

Are there any shortcuts to (stringByAppendingString:) string concatenation in Objective-C, or shortcuts for working with NSString in general?
For example, I'd like to make:
NSString *myString = #"This";
NSString *test = [myString stringByAppendingString:#" is just a test"];
something more like:
string myString = "This";
string test = myString + " is just a test";
An option:
[NSString stringWithFormat:#"%#/%#/%#", one, two, three];
Another option:
I'm guessing you're not happy with multiple appends (a+b+c+d), in which case you could do:
NSLog(#"%#", [Util append:one, #" ", two, nil]); // "one two"
NSLog(#"%#", [Util append:three, #"/", two, #"/", one, nil]); // three/two/one
using something like
+ (NSString *) append:(id) first, ...
{
NSString * result = #"";
id eachArg;
va_list alist;
if(first)
{
result = [result stringByAppendingString:first];
va_start(alist, first);
while (eachArg = va_arg(alist, id))
result = [result stringByAppendingString:eachArg];
va_end(alist);
}
return result;
}
Two answers I can think of... neither is particularly as pleasant as just having a concatenation operator.
First, use an NSMutableString, which has an appendString method, removing some of the need for extra temp strings.
Second, use an NSArray to concatenate via the componentsJoinedByString method.
If you have 2 NSString literals, you can also just do this:
NSString *joinedFromLiterals = #"ONE " #"MILLION " #"YEARS " #"DUNGEON!!!";
That's also useful for joining #defines:
#define STRINGA #"Also, I don't know "
#define STRINGB #"where food comes from."
#define JOINED STRINGA STRINGB
Enjoy.
I keep returning to this post and always end up sorting through the answers to find this simple solution that works with as many variables as needed:
[NSString stringWithFormat:#"%#/%#/%#", three, two, one];
For example:
NSString *urlForHttpGet = [NSString stringWithFormat:#"http://example.com/login/username/%#/userid/%i", userName, userId];
Create a method:
- (NSString *)strCat: (NSString *)one: (NSString *)two
{
NSString *myString;
myString = [NSString stringWithFormat:#"%#%#", one , two];
return myString;
}
Then, in whatever function you need it in, set your string or text field or whatever to the return value of this function.
Or, to make a shortcut, convert the NSString into a C++ string and use the '+' there.
Well, as colon is kind of special symbol, but is part of method signature, it is possible to exted the NSString with category to add this non-idiomatic style of string concatenation:
[#"This " : #"feels " : #"almost like " : #"concatenation with operators"];
You can define as many colon separated arguments as you find useful... ;-)
For a good measure, I've also added concat: with variable arguments that takes nil terminated list of strings.
// NSString+Concatenation.h
#import <Foundation/Foundation.h>
#interface NSString (Concatenation)
- (NSString *):(NSString *)a;
- (NSString *):(NSString *)a :(NSString *)b;
- (NSString *):(NSString *)a :(NSString *)b :(NSString *)c;
- (NSString *):(NSString *)a :(NSString *)b :(NSString *)c :(NSString *)d;
- (NSString *)concat:(NSString *)strings, ...;
#end
// NSString+Concatenation.m
#import "NSString+Concatenation.h"
#implementation NSString (Concatenation)
- (NSString *):(NSString *)a { return [self stringByAppendingString:a];}
- (NSString *):(NSString *)a :(NSString *)b { return [[self:a]:b];}
- (NSString *):(NSString *)a :(NSString *)b :(NSString *)c
{ return [[[self:a]:b]:c]; }
- (NSString *):(NSString *)a :(NSString *)b :(NSString *)c :(NSString *)d
{ return [[[[self:a]:b]:c]:d];}
- (NSString *)concat:(NSString *)strings, ...
{
va_list args;
va_start(args, strings);
NSString *s;
NSString *con = [self stringByAppendingString:strings];
while((s = va_arg(args, NSString *)))
con = [con stringByAppendingString:s];
va_end(args);
return con;
}
#end
// NSString+ConcatenationTest.h
#import <SenTestingKit/SenTestingKit.h>
#import "NSString+Concatenation.h"
#interface NSString_ConcatenationTest : SenTestCase
#end
// NSString+ConcatenationTest.m
#import "NSString+ConcatenationTest.h"
#implementation NSString_ConcatenationTest
- (void)testSimpleConcatenation
{
STAssertEqualObjects([#"a":#"b"], #"ab", nil);
STAssertEqualObjects([#"a":#"b":#"c"], #"abc", nil);
STAssertEqualObjects([#"a":#"b":#"c":#"d"], #"abcd", nil);
STAssertEqualObjects([#"a":#"b":#"c":#"d":#"e"], #"abcde", nil);
STAssertEqualObjects([#"this " : #"is " : #"string " : #"concatenation"],
#"this is string concatenation", nil);
}
- (void)testVarArgConcatenation
{
NSString *concatenation = [#"a" concat:#"b", nil];
STAssertEqualObjects(concatenation, #"ab", nil);
concatenation = [concatenation concat:#"c", #"d", concatenation, nil];
STAssertEqualObjects(concatenation, #"abcdab", nil);
}
Use stringByAppendingString: this way:
NSString *string1, *string2, *result;
string1 = #"This is ";
string2 = #"my string.";
result = [result stringByAppendingString:string1];
result = [result stringByAppendingString:string2];
OR
result = [result stringByAppendingString:#"This is "];
result = [result stringByAppendingString:#"my string."];
Macro:
// stringConcat(...)
// A shortcut for concatenating strings (or objects' string representations).
// Input: Any number of non-nil NSObjects.
// Output: All arguments concatenated together into a single NSString.
#define stringConcat(...) \
[#[__VA_ARGS__] componentsJoinedByString:#""]
Test Cases:
- (void)testStringConcat {
NSString *actual;
actual = stringConcat(); //might not make sense, but it's still a valid expression.
STAssertEqualObjects(#"", actual, #"stringConcat");
actual = stringConcat(#"A");
STAssertEqualObjects(#"A", actual, #"stringConcat");
actual = stringConcat(#"A", #"B");
STAssertEqualObjects(#"AB", actual, #"stringConcat");
actual = stringConcat(#"A", #"B", #"C");
STAssertEqualObjects(#"ABC", actual, #"stringConcat");
// works on all NSObjects (not just strings):
actual = stringConcat(#1, #" ", #2, #" ", #3);
STAssertEqualObjects(#"1 2 3", actual, #"stringConcat");
}
Alternate macro: (if you wanted to enforce a minimum number of arguments)
// stringConcat(...)
// A shortcut for concatenating strings (or objects' string representations).
// Input: Two or more non-nil NSObjects.
// Output: All arguments concatenated together into a single NSString.
#define stringConcat(str1, str2, ...) \
[#[ str1, str2, ##__VA_ARGS__] componentsJoinedByString:#""];
When building requests for web services, I find doing something like the following is very easy and makes concatenation readable in Xcode:
NSString* postBody = {
#"<?xml version=\"1.0\" encoding=\"utf-8\"?>"
#"<soap:Envelope xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" xmlns:soap=\"http://schemas.xmlsoap.org/soap/envelope/\">"
#" <soap:Body>"
#" <WebServiceMethod xmlns=\"\">"
#" <parameter>test</parameter>"
#" </WebServiceMethod>"
#" </soap:Body>"
#"</soap:Envelope>"
};
Shortcut by creating AppendString (AS) macro ...
#define AS(A,B) [(A) stringByAppendingString:(B)]
NSString *myString = #"This";
NSString *test = AS(myString,#" is just a test");
Note:
If using a macro, of course just do it with variadic arguments, see EthanB's answer.
NSString *label1 = #"Process Name: ";
NSString *label2 = #"Process Id: ";
NSString *processName = [[NSProcessInfo processInfo] processName];
NSString *processID = [NSString stringWithFormat:#"%d", [[NSProcessInfo processInfo] processIdentifier]];
NSString *testConcat = [NSString stringWithFormat:#"%# %# %# %#", label1, processName, label2, processID];
Here's a simple way, using the new array literal syntax:
NSString * s = [#[#"one ", #"two ", #"three"] componentsJoinedByString:#""];
^^^^^^^ create array ^^^^^
^^^^^^^ concatenate ^^^^^
NSString *myString = #"This";
NSString *test = [myString stringByAppendingString:#" is just a test"];
After a couple of years now with Objective C I think this is the best way to work with Objective C to achieve what you are trying to achieve.
Start keying in "N" in your Xcode application and it autocompletes to "NSString".
key in "str" and it autocompletes to "stringByAppendingString". So the keystrokes are quite limited.
Once you get the hang of hitting the "#" key and tabbing the process of writing readable code no longer becomes a problem. It is just a matter of adapting.
The only way to make c = [a stringByAppendingString: b] any shorter is to use autocomplete at around the st point. The + operator is part of C, which doesn't know about Objective-C objects.
How about shortening stringByAppendingString and use a #define:
#define and stringByAppendingString
Thus you would use:
NSString* myString = [#"Hello " and #"world"];
Problem is that it only works for two strings, you're required to wrap additional brackets for more appends:
NSString* myString = [[#"Hello" and: #" world"] and: #" again"];
NSString *result=[NSString stringWithFormat:#"%# %#", #"Hello", #"World"];
NSString *label1 = #"Process Name: ";
NSString *label2 = #"Process Id: ";
NSString *processName = [[NSProcessInfo processInfo] processName];
NSString *processID = [NSString stringWithFormat:#"%d", [[NSProcessInfo processInfo] processIdentifier]];
NSString *testConcat = [NSString stringWithFormat:#"%# %# %# %#", label1, processName, label2, processID];
I tried this code. it's worked for me.
NSMutableString * myString=[[NSMutableString alloc]init];
myString=[myString stringByAppendingString:#"first value"];
myString=[myString stringByAppendingString:#"second string"];
Was trying the following in the lldb pane
[NSString stringWithFormat:#"%#/%#/%#", three, two, one];
which errors.
instead use alloc and initWithFormat method:
[[NSString alloc] initWithFormat:#"%#/%#/%#", #"three", #"two", #"one"];
This is for better logging, and logging only - based on dicius excellent multiple argument method. I define a Logger class, and call it like so:
[Logger log: #"foobar ", #" asdads ", theString, nil];
Almost good, except having to end the var args with "nil" but I suppose there's no way around that in Objective-C.
Logger.h
#interface Logger : NSObject {
}
+ (void) log: (id) first, ...;
#end
Logger.m
#implementation Logger
+ (void) log: (id) first, ...
{
// TODO: make efficient; handle arguments other than strings
// thanks to #diciu http://stackoverflow.com/questions/510269/how-do-i-concatenate-strings-in-objective-c
NSString * result = #"";
id eachArg;
va_list alist;
if(first)
{
result = [result stringByAppendingString:first];
va_start(alist, first);
while (eachArg = va_arg(alist, id))
{
result = [result stringByAppendingString:eachArg];
}
va_end(alist);
}
NSLog(#"%#", result);
}
#end
In order to only concat strings, I'd define a Category on NSString and add a static (+) concatenate method to it that looks exactly like the log method above except it returns the string. It's on NSString because it's a string method, and it's static because you want to create a new string from 1-N strings, not call it on any one of the strings that are part of the append.
NSNumber *lat = [NSNumber numberWithDouble:destinationMapView.camera.target.latitude];
NSNumber *lon = [NSNumber numberWithDouble:destinationMapView.camera.target.longitude];
NSString *DesconCatenated = [NSString stringWithFormat:#"%#|%#",lat,lon];
Try stringWithFormat:
NSString *myString = [NSString stringWithFormat:#"%# %# %# %d", "The", "Answer", "Is", 42];
When dealing with strings often I find it easier to make the source file ObjC++, then I can concatenate std::strings using the second method shown in the question.
std::string stdstr = [nsstr UTF8String];
//easier to read and more portable string manipulation goes here...
NSString* nsstr = [NSString stringWithUTF8String:stdstr.c_str()];
My preferred method is this:
NSString *firstString = #"foo";
NSString *secondString = #"bar";
NSString *thirdString = #"baz";
NSString *joinedString = [#[firstString, secondString, thirdString] join];
You can achieve it by adding the join method to NSArray with a category:
#import "NSArray+Join.h"
#implementation NSArray (Join)
-(NSString *)join
{
return [self componentsJoinedByString:#""];
}
#end
#[] it's the short definition for NSArray, I think this is the fastest method to concatenate strings.
If you don't want to use the category, use directly the componentsJoinedByString: method:
NSString *joinedString = [#[firstString, secondString, thirdString] componentsJoinedByString:#""];
You can use NSArray as
NSString *string1=#"This"
NSString *string2=#"is just"
NSString *string3=#"a test"
NSArray *myStrings = [[NSArray alloc] initWithObjects:string1, string2, string3,nil];
NSString *fullLengthString = [myStrings componentsJoinedByString:#" "];
or
you can use
NSString *imageFullName=[NSString stringWithFormat:#"%# %# %#.", string1,string2,string3];
Either of these formats work in XCode7 when I tested:
NSString *sTest1 = {#"This" " and that" " and one more"};
NSString *sTest2 = {
#"This"
" and that"
" and one more"
};
NSLog(#"\n%#\n\n%#",sTest1,sTest2);
For some reason, you only need the # operator character on the first string of the mix.
However, it doesn't work with variable insertion. For that, you can use this extremely simple solution with the exception of using a macro on "cat" instead of "and".
For all Objective C lovers that need this in a UI-Test:
-(void) clearTextField:(XCUIElement*) textField{
NSString* currentInput = (NSString*) textField.value;
NSMutableString* deleteString = [NSMutableString new];
for(int i = 0; i < currentInput.length; ++i) {
[deleteString appendString: [NSString stringWithFormat:#"%c", 8]];
}
[textField typeText:deleteString];
}
listOfCatalogIDs =[#[#"id[]=",listOfCatalogIDs] componentsJoinedByString:#""];
Let's imagine that u don't know how many strings there.
NSMutableArray *arrForStrings = [[NSMutableArray alloc] init];
for (int i=0; i<[allMyStrings count]; i++) {
NSString *str = [allMyStrings objectAtIndex:i];
[arrForStrings addObject:str];
}
NSString *readyString = [[arrForStrings mutableCopy] componentsJoinedByString:#", "];
Inspired by NSMutableString idea from Chris, I make a perfect macro imho.
It supports insert nil elements without any Exceptions.
#import <libextobjc/metamacros.h>
#define STR_CONCAT(...) \
({ \
__auto_type str__ = [NSMutableString string]; \
metamacro_foreach_cxt(never_use_immediately_str_concatify_,, str__, __VA_ARGS__) \
(NSString *)str__.copy; \
})
#define never_use_immediately_str_concatify_(INDEX, CONTEXT, VAR) \
[CONTEXT appendString:VAR ?: #""];
Example:
STR_CONCAT(#"button_bg_", #(count).stringValue, #".png");
// button_bg_2.png
If you like, you can use id type as parameter by using [VAR description] instead of NSString.