How to count items in plist - objective-c

I got a question how i can count items in my plist file. I tried:
NSString *bundlePathofPlist = [[NSBundle mainBundle]pathForResource:#"Mything" ofType:#"plist"];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:bundlePathofPlist];
NSArray *dataFromPlist = [dict valueForKey:#"some"];
for(int i =0;i<[dataFromPlist count];i++)
{
//NSLog(#"%#",[dataFromPlist objectAtIndex:i]);
[self setTableData:[dataFromPlist count]];
}
NSLog(#"%#", tableData);
But on this line an error appears:
[self setTableData:[dataFromPlist count]];
Implicit conversion of 'NSUInteger' (aka 'unsigned int') to 'NSArray *' is disallowed with ARC
and warning:
Incompatible integer to pointer conversion sending 'NSUInteger' (aka 'unsigned int') to parameter of type 'NSArray *';

It looks like your setTableData takes an NSArray instance. You need to prepare an array upfront in a loop, and then set it once, like this:
NSMutableArray *data = [NSMutableArray array];
for(int i =0;i<[dataFromPlist count];i++)
{
//NSLog(#"%#",[dataFromPlist objectAtIndex:i]);
[data addObject:[NSNumber numberWithInt:[[dataFromPlist objectAtIndex:i] count]]];
}
[self setTableData:data];
This assumes that your setTableData method expects an array of NSNumber instances wrapping ints.

The problem is you're using [dataFromPlist count] inside your for loop, which makes no sense. You probably meant [dataFromPlist objectAtIndex:i].
Or, more idiomatically,
for (NSArray *elt in dataFromPlist) {
[self setTableData:elt];
}
Although I do have to wonder why you're calling -setTableData: over and over with different elements.

Related

-[__NSArrayI floatValue]: unrecognized selector sent to instance

_data = [NSMutableArray new];
NSNumber *value1 = [NSNumber numberWithFloat: 5.0f];
[_data setValue:value1 forKey:#"foothold"];
NSNumber *value2 = [_data valueForKey:#"foothold"];
NSLog(#"a foothold %f ",[value2 floatValue]);//error here
It's strange, but I don't see my error...
You have a few problems.
_data is mistakenly an NSMutableArray instead of an NSMutableDictionary.
Don't use setValue:forKey: and valueForKey: unless you mean to do KVC.
Use modern syntax (it's easier and it avoid issue #2).
Updated code:
_data = [NSMutableDictionary dictionary];
NSNumber *value1 = #5.0;
_data[#"foothold"] = value1;
NSNumber *value2 = _data[#"foothold"];
NSLog(#"a foothold %f ",[value2 floatValue]);
When you get an "unrecognized selector sent to instance" error, you'll be given the name of the method you're attempting to call (in this case floatValue) as well as the type of object you're calling it on (NSArray here).
So in this case, despite value2 being declared as an NSNumber, the value returned from [_data valueForKey:#"foothold"]; is an NSArray, which does not respond to the floatValue selector.
I'm surprised you weren't given other warnings. Given the syntax, it looks like you should be using an NSMutableDictionary rather than an array. In which case, try this:
NSMutableDictionary *_data = [NSMutableDictionary dictionary];
NSNumber *value1 = #5.0f;
_data[#"foothold"] = value1;
NSNumber *value2 = _data[#"foothold"];
NSLog(#"a foothold %f", [value2 floatValue]);

-[__NSArrayI replaceObjectAtIndex:withObject:]: unrecognized selector sent to instance

I've got an Mutable NSArray storing object types (ids - NSNumber, NSString) that will store digits (1,2,3,4, etc), operations (+,-,/,*, etc.), and variables (x,y,z, etc.). I have the variables and related values stored in an NSDictionary, keys are NSStrings equal to x,y,z with NSNumber values 5,5,2 respectively. I want to replace my variable in my NSArray with the actual value stored in my NSDictionary. I keep getting the following error when I attempt to replace the object. Please help.
-[__NSArrayI replaceObjectAtIndex:withObject:]: unrecognized selector sent to instance
NSDictionary *dict = [[NSDictionary alloc] initWithObjectsAndKeys:
[NSNumber numberWithDouble:5],#"x",
[NSNumber numberWithDouble:5],#"y",
[NSNumber numberWithDouble:2],#"z",
nil];
+ (double)runProgram:(id)program
usingVariableValues:(NSDictionary *)variableValues;
{
// introspection - ensure program is NSArray and variableValues is NSDictionary
if ([program isKindOfClass:[NSArray class]] && [variableValues isKindOfClass: [NSDictionary class]])
{
// array for program stack
NSMutableArray *stack = [program copy];
// Loop to replace variables with actual values in NSDictionary
for (NSUInteger i = 0; i < [stack count]; i++)
{
NSLog(#"object at index = %#", [stack objectAtIndex:i]);
if ([[stack objectAtIndex:i] isKindOfClass:[NSString class]] && [[stack objectAtIndex:i] isEqualToString: #"x"])
{
// replace variable with value in corresponding value in dictionary
NSNumber *numberKeyValue = [variableValues objectForKey:#"x"];
[stack replaceObjectAtIndex:i withObject:numberKeyValue];
}
}
return 0
}
NSArray's copy method always returns an immutable array, regardless of whether the original was mutable or immutable. If you want a mutable copy, you need to use mutableCopy. (It's a Cocoa convention. NSString, NSDictionary, and generally any class that has mutable and immutable variants will work the same way.)

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.

NSArray filled with bool

I am trying to create an NSArray of bool values. How many I do this please?
NSArray *array = [[NSArray alloc] init];
array[0] = YES;
this does not work for me.
Thanks
NSArrays are not c-arrays. You cant access the values of an NSArray with array[foo];
But you can use c type arrays inside objective-C without problems.
The Objective-C approach would be:
NSMutableArray *array = [[NSMutableArray alloc] init];
[array addObject:[NSNumber numberWithBool:YES]];
//or
[array addObject:#(NO)];
...
BOOL b = [[array objectAtIndex:0] boolValue];
....
[array release];
EDIT: New versions of clang, the now standard compiler for objective-c, understand Object subscripting. When you use a new version of clang you will be able to use array[0] = #YES
Seems like you've confused c array with objc NSArray. NSArray is more like a list in Java, into which you can add objects, but not values like NSInteger, BOOL, double etc. If you wish to store such values in an NSArray, you first need to create a mutable array:
NSMutableArray* array = [[NSMutableArray alloc] init];
And then add proper object to it (in this case we'll use NSNumber to store your BOOL value):
[array addObject:[NSNumber numberWithBool:yourBoolValue]];
And that's pretty much it! If you wish to access the bool value, just call:
BOOL yourBoolValue = [[array objectAtIndex:0] boolValue];
Cheers,
Pawel
Use [NSNumber numberWithBool: YES] to get an object you can put in the collection.

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.