Concatenating NSArray contents with NSMutableString AppendString - objective-c

I'm trying to iterate through an NSArray and keep getting a compiler error right when i try concatenating the contents of my array at position i to my NSMutableString instance..
It just tells me that there's a "syntax error before ;" which doesn't tell me a whole lot.
at this line:
[output appendString:[widget.children objectAtIndex:i];
i know there must be something up with my syntax..
my function is as follows
- (NSString *)readArray
{
NSMutableString *output = [[NSMutableString alloc] init];
int i;
int arraySize = widget.children.count;
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
for (i = 0; i < arraySize; i++)
{
[output appendString:[widget.children objectAtIndex:i]; (throws error here)
}
[pool release];
return output;
}
thanks in advance

NSArray has a method that does exactly what you're doing as well:
- (NSString *)readArray {
return [widget.children componentsJoinedByString:#""];
}
Also, unless you're calling that function fairly frequently in a tight loop there's not much advantage to having it create its' own autorelease pool.

you have an unclosed bracket
you need ]] at the end instead of ]

Related

Why does replaceObjectAtIndex depend on whether or not I use a new definition in the loop?

I have two codes. Not working is the following:
NSMutableArray *tmpArray = [[NSMutableArray alloc] init];
for (int i=0; i<[dataSetArray count]; i++) {
tmpArray = (NSMutableArray *) [dataSetArray objectAtIndex:i];
// OR use: tmpArray = dataSetArray[i]
... doing stuff
[tmpArray replaceObjectAtIndex:0 withObject:tmpStr];
}
While this works:
for (int i=0; i<[dataSetArray count]; i++) {
NSMutableArray *tmpArray = [[NSMutableArray alloc] initWithArray:[dataSetArray objectAtIndex:i]];
... doing stuff
[tmpArray replaceObjectAtIndex:0 withObject:tmpStr];
}
Two questions:
The first code doesn't yield an NSMutableArray. Why? I declare it
above.
Is there a better way to obtain the same result. I just
dislike defining variables in a loop. This makes the code
unreadable.
--- edit:
Here the full code:
Datatypes are:
dataSetArray: NSMutableArray. However, its contents (i.e. dataSetArray[i]) are NSArrays (I read them into the program from an excel file).
NSString *tmpStr = [[NSString alloc] init];
for (int i=0; i<[dataSetArray count]; i++) {
NSMutableArray *tmpArray = [[NSMutableArray alloc] initWithArray:[dataSetArray objectAtIndex:i]];
for (int j=0; j<[tmpArray count]; j++) {
if ( [dataSetArray[0][j] isEqualToString:#"Number"] ) {continue;}
tmpStr = (NSString *) [tmpArray objectAtIndex:j];
// replace single backslash by double-backslash:
tmpStr = [tmpStr stringByReplacingOccurrencesOfString:#"\\" withString:#"\\\\"];
// replace first dollar sign by "<p>\\[" and second by "\\]</p>"
// Use a methode defined in the NSString+Extension
tmpStr = [tmpStr replaceTexFormulaSigns:tmpStr];
//NSLog(#"j=%d", j);
//NSLog(#"tmpArray is of type: %#", [tmpArray class]);
//NSLog(#" tmpStr is of type: %#", [tmpStr class]);
[tmpArray replaceObjectAtIndex:j withObject:tmpStr];
}
[dataSetArray replaceObjectAtIndex:i withObject:tmpArray];
}
So even if I use your suggestion, I am still facing the same problem with the inner array.
The first code doesn't yield a NSMutableArray. Why? I declare it above.
The declaration of the reference variable tmpArray does not change the type of the referred object. It is still an (immutable) array.
The creation of the mutable array at the very beginning of the first snippet is without any meaning, because the reference to it is overridden.
Is there a better way to obtain the same result. I just dislike defining variables in a loop. This makes the code unreadable.
Yes. The second example works in a way, but do something completely different. (It always creates a new array with a single item in it. No, that's not true. It shouldn't compile at all.)
You should do:
NSMutableArray *tmpArray = [dataSetArray mutableCopy];
for (int i=0; i<[dataSetArray count]; i++)
{
…
[tmpArray replaceObjectAtIndex:i withObject:tmpStr];
}
You should really get some additional knowledge about objects and object references.

How to view the entire content of an array in a label (xcode 4.1)

i'm programming in Obj-c with xcode4.1, i have an array with numbers in it, and i want to visualize all of them in a label...can anyone help me around this please?
thanks!
this is the code:
combinedString=[[NSMutableArray alloc] init];
NSString *finalStringLabel=#"";
for (i=0; i<=textLength; i++) {
//character coding
char myChar = [myString characterAtIndex:i];
NSString *myCharS=[NSString stringWithFormat:#"%c", myChar];
int asciiCode=[myCharS characterAtIndex:0];
NSString *asciiS=[NSString stringWithFormat:#"%i", asciiCode];
[combinedString addObject:asciiS];
}
finalStringLabel=[NSString stringWithFormat:#"", [combinedString componentsJoinedByString:#"."]];
myLabel.text=finalStringLabel;
[combinedString release];
}
You can use this
NSArray *yourArray;
NSString *createdString = [yourArray componentsJoinedByString:#" "];
myLabel.text = createdString;
As your array is combinedString,
combinedString=[[NSMutableArray alloc] init];
looks like you are providing values after this line or this is not a property (this is a local as you are releasing it later), and your code in not complete.
Anyways,
You don't need to create an empty string and then assign new object to it, need to do as :
myLabel.text=[combinedString componentsJoinedByString:#"."];
[combinedString release];
}

Method Creates an Array with 11 objects, All Out of Scope, Unrecognized Selector Results

Okay, so, I'm doing a simple lookup. I have an array of NSString objects and a string to search for in the array's elements.
It all seems to work up until I try to add a match to a new mutable array made to hold the search results. The stringHolder variable gets the string, and resultsCollectorArray even get the right number of new elements, but each element is empty and "out of range". Here's the method:
#implementation NSArray (checkForString)
-(NSMutableArray *) checkForString: (NSString *) matchSought
{
long unsigned numberofArrayElements;
long unsigned loop = 0;
NSRange searchResults;
NSMutableArray * resultCollectorArray = [[NSMutableSet alloc] init];
id stringHolder;
numberofArrayElements = [self count];
while (loop < numberofArrayElements) {
searchResults.length = 0;
searchResults = [[self objectAtIndex: loop] rangeOfString: matchSought options:NSCaseInsensitiveSearch];
if (searchResults.length > 0) {
stringHolder = [self objectAtIndex: loop];
[resultCollectorArray addObject: stringHolder];
}
loop++;
}
return [resultCollectorArray autorelease];
}
Once we get back to the main portion of the program, I get an unrecognized selector sent to the mutable array that was supposed to receive the result of the method. Here's the main section:
#import <Foundation/Foundation.h>
#import "LookupInArray.h"
int main (int argc, const char * argv[])
{
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
NSString *testString = [[NSString alloc] initWithString: #"ab"];
NSMutableString * resultString = [[NSString alloc] init];
NSArray * theArray = [[NSArray alloc] initWithObjects: ..., nil]; // Actual code has the objects
NSMutableArray *resultArray = [[NSMutableArray alloc] init];
NSUInteger arrayCount = 0;
unsigned long loops = 0;
resultArray = [theArray checkForString: testString];
arrayCount = [resultArray count];
while (loops < arrayCount){
resultString = [resultArray objectAtIndex: loops]; // Here's where we get the unrecognized selector.
NSLog(#"%#", resultString);
loops++;
}
[pool drain]; // Also, I'll release the objects later. I just want to get what's above working first.
return 0;
}
I've searched the other answers (for hours now), but didn't seen anything that solved the issue.
Any and all help would be really appreciated.
And thanks beforehand.
NSMutableArray * resultCollectorArray = [[NSMutableSet alloc] init]; is so incorrect. You are creating a mutable set and assigning it to a mutable array.
You are getting unrecognized selector because objectAtIndex: is not a valid selector for NSMutableSet. Make that statement,
NSMutableArray * resultCollectorArray = [[NSMutableArray alloc] init];
A Better way
NSArray * filteredArray = [array filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"SELF contains[cd] %#", searchString]];
You can directly filter the array using predicates. This way you do this in a single step. :)

Accept string values in NSArray from the user

hi i want to accept string values into the object of NSArray at run time from the user heres what i tried
-(void)fun
{
NSArray *arr = [[NSArray alloc]init];
for(int i =0;i<3;i++)
{
scanf("%s",&arr[i]);
}
printf("Print values\n");
for(int j =0; j<3;j++)
{
printf("\n%s",arr[j]);
}
}
i am getting an error can you please help me out regarding this and is their any alternative to scanf in objective c.
Thank you
scanf() with a %s format will read the string into a C array, not an NSArray object. You need to read the string into a C array, then make an NSString object to add to your NSArray. You also need to have a mutable array to make your code work. Example:
NSMutableArray *arr = [[NSMutableArray alloc] initWithCapacity:3];
for (int i = 0; i < 3; i++)
{
char buf[100];
scanf("%s", buf);
NSString *str = [NSString stringWithCString:buf encoding:NSASCIIStringEncoding];
[arr addObject:str];
}
You can use NSLog() to print your strings later on.
use NSMutableArray instead;
than you can use also
[arr addObject:tempVar];

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

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.