I have multiple NSDictionaries inside an NSMutableArray. A short hand example is below. comparisonChart is the NSMutableArray.
NSDictionary *dict1 = #{
#"Tube": #"10/0",
#"Dress":#"3"
};
[self.comparisonChart setValue:dict1 forKey#"0"];
// key 0 as i wish to use numeric indexes, comparisonChart is mutable array
When i wish to extract the value for a key i've tried:
[self.comparisonChart valueForKey:[NSString stringWithFormat:#"%ld", value]]
//where value is numeric value e.g 0
However this returns null. ive also tried objectForKey:value with the same result.
How do i go about this?
update:
[self.comparisonChart insertObject:dict23 atIndex:1];
NSLog(#"chart: %#", [self.comparisonChart objectAtIndex:1]);
Output: chart: (null) // why is this?
If self.comparisonChart is a NSMutableArray then you add the NSDictionary like so:
[self.comparisonChart addObject: dict1];
or you may specify the index like so:
[self.comparisonChart insertObject: dict1 atIndex:desiredIndex];
To retrieve the NSDictionary object you must call:
[self.comparisonChart objectAtIndex:indexNumber];
You need to use objectForKey for one, you should also be using subscripts instead;
NSDictionary *dict1 = #{#"Tube": #"10/0",
#"Dress":#"3"};
NSMutableDictionary *comparisonChart = [NSMutableDictionary new];
comparisonChart[#"0"] = dict1;
NSLog(#"My value: %#", comparisonChart[#"0"]);
Here's the code copy and pasted from AppDelegate and working:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
NSDictionary *dict1 = #{#"Tube": #"10/0",
#"Dress":#"3"};
NSMutableDictionary *comparisonChart = [NSMutableDictionary new];
comparisonChart[#"0"] = dict1;
NSLog(#"My value: %#", comparisonChart[#"0"]);
return YES;
}
Edit:
To append on the question above please ensure the index being inserted is valid per the documentation below:
Apple documentation
- (void)insertObject:(id)anObject
atIndex:(NSUInteger)index
Parameters
anObject
The object to add to the array's content. This value must not be nil.
IMPORTANT
Raises an NSInvalidArgumentException if anObject is nil.
index
The index in the array at which to insert anObject. This value must not be greater than the count of elements in the array.
IMPORTANT
Raises an NSRangeException if index is greater than the number of elements in the array.
Discussion
If index is already occupied, the objects at index and beyond are shifted by adding 1 to their indices to make room.
Note that NSArray objects are not like C arrays. That is, even though you specify a size when you create an array, the specified size
is regarded as a “hint”; the actual size of the array is still 0. This
means that you cannot insert an object at an index greater than the
current count of an array. For example, if an array contains two
objects, its size is 2, so you can add objects at indices 0, 1, or 2.
Index 3 is illegal and out of bounds; if you try to add an object at
index 3 (when the size of the array is 2), NSMutableArray raises an
exception.
Import Statement
import Foundation
Availability
Available in iOS 2.0 and later.
Related
I have an array inside a NSMutableDictionary and i want to add objects to it. With my current approach I get an error saying that the array is immutable.
I think the problem lies when I´m saving the dictionary to NSUserDefaults. I´m retrieving the is it a NSDictionary but I am at the same time creating a new NSMutableDictionary with the contents.
However, the array seems to be immutable. How do I replace an array inside of a dictionary?
My dictionary looks like this:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0], nil];
NSDictionary *dict = #{
#"firstKey": #{
#"theArray":array,
}
};
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
I am trying to add objects like this:
[[[mutDict objectForKey:#"firstKey"] objectForKey:#"theArray"] addObject:[NSNumber numberWithInt:5]];
I am able to add objects to the array inside mutDict before its saved to NSUserDefaults
The error message I get when I try to add to the array inside the dictionary after loading it from NSUserDefaults:
Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: '-[__NSCFArray insertObject:atIndex:]: mutating method sent to immutable object'
Here's what the documentation for dictionaryForKey: says on NSUserDefaults:
Special Considerations
The returned dictionary and its contents are immutable, even if the values you >originally set were mutable.
So when you retrieve your dictionary from NSUserDefaults the dictionary itself and all of the collections inside it are immutable. You can make the top level dictionary mutable (which I assume you are doing), but that won't propagate down into the now immutable NSArrays which are values in the dictionary.
The only way to get around this is to go through the dictionary that's returned and replace the immutable NSArrays with their mutable counterparts. It might look something like this.
- (NSMutableDictionary *)deepMutableCopyOfDictionary:(NSDictionary *)dictionary
{
NSMutableDictionary *mutableDictionary = [dictionary mutableCopy];
for (id key in [mutableDictionary allKeys]) {
id value = mutableDictionary[key];
if ([value isKindOfClass:[NSDictionary class]]) {
// If the value is a dictionary make it mutable and call recursively
mutableDictionary[key] = [self deepMutableCopyOfDictionary:dictionary[key]];
}
else if ([value isKindOfClass:[NSArray class]]) {
// If the value is an array, make it mutable
mutableDictionary[key] = [(NSArray *)value mutableCopy];
}
}
return mutableDictionary;
}
To be honest though it sounds like you're using NSUserDefaults for something a lot more complex then it is intended for. If you want to persist complex data structures then you should look into something like Core Data, or if that looks to be a bit overkill take a look at NSKeyedArchiver.
You can add object directly to the array:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0], nil];
NSDictionary *dict = #{
#"firstKey": #{
#"theArray":array,
}
};
NSMutableDictionary *mutDict = [[NSMutableDictionary alloc] initWithDictionary:dict];
//Since Objective-C objects are always passed by reference (using pointers) you can add object to the array
[array addObject:[NSNumber numberWithInt:55]];
Swift example of adding object to array which is part of a dictionary.
let arr = [0] // note that initial array may be immutable
var dict = ["fK": ["a":arr]] // even if "arr" will be mutable, but "dict" immutable
dict["fK"]!["a"]!.append(3) // this will not work. "dict" must be mutable
println(dict) //[fK: [a: [0, 3]]]
Another approach
var arr = [0] // initial array must be mutable
var dict = ["fK": ["a":arr]] // in both cases dictionary must be mutable
arr.append(3)
let newArr = arr
dict["fK"]!["a"]! = newArr // because we change it's content
println(dict) //[fK: [a: [0, 3]]]
I have 3 objects that might be or not initialized in a random order.
so, if objects "objectOne, "objectTwo", "objectThree" are initialized in this order with
myArray = [NSArray arrayWithObjects:objectOne,objectTwo,objectThree nil];
all objects get inside the array without problem but in my case objectOne, objectTwo might be nil and objectThree might not be nil, and in this case I would like myArray to return(count) 1.
if objectOne is nil but objectTwo and objectThree are not nil I want my array to return(count) 2.
In these 2 last cases my array always return nil. What would be the best approach to this?
There are no magic method can solve the problem for you, you need to build the array from NSMutableArray
NSMutableArray *array = [NSMutableArray array];
if (objectOne) [array addObject:objectOne];
if (objectTwo) [array addObject:objectTwo];
if (objectThree) [array addObject:objectThree];
arrays can't contain nil. There is a special object, NSNull ([NSNull null]), that serves as a placeholder for nil. You can put NSNull in an array, but I don't think that solves your problem either.
How about this:
Create an empty mutable array.
In 3 separate statements:
If objectOne is not nil, add it to the array
if objectTwo is not nil, add it to the array
If objectThree is not nil, add it to the array.
If you need your objects to be in random order, scramble the array afterwords:
for (int index = 0; index < array.count; index++)
{
int randomIndex = arc4random_uniform()
[array exchangeObjectAtIndex: index withObjectAtIndex: randomIndex];
}
This is known as a Fisher–Yates shuffle. (or a minor variation on Fisher-Yates, anyway.)
If you're doing this rarely and you aren't trying to make things neat, you can, of course, use a mutable array and either add or don't add the items one at a time in code, depending on whether they are nil.
If you're doing this frequently and you want a syntax that looks similar to the array literal notation, you can take advantage of the C preprocessor and C arrays to create a smarter NSArray class constructor that handles nil:
#define NSArrayWithCArray(array) \
[NSArray arrayWithCArray:cArray count:sizeof(cArray) / sizeof(cArray[0])];
id cArray[] = {
object1,
object2,
object3,
...
};
NSArray *array = NSArrayWithCArray(cArray);
and then define a method on NSObject that walks through the C array programmatically, dropping any nil values.
+ (NSArray *)arrayWithCArray:(__strong id[])cArray count:(NSUInteger)count {
NSMutableArray *array = [NSMutableArray arrayWithCapacity:count];
for (__strong id *item = cArray; item < cArray + count; item++) {
if (*item != nil) {
[array addObject:*item];
}
}
return array;
}
Note: The code above is untested, but at least close enough to give you an idea of how to do it. :-)
I've got a .plist like this:
Click image for full resolution
How can I create different arrays for each subpart of items. like NSArray foodName with contents of item 0's item 1's to item n's foodName
There are lots of items.
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,
NSUserDomainMask, YES);
self.pFile = [[paths objectAtIndex:0]
stringByAppendingPathComponent:#"recipes.plist"];
self.plist = [[NSMutableDictionary alloc] initWithContentsOfFile:pFile];
if (!plist) {
self.plist = [NSMutableDictionary new];
[plist writeToFile:pFile atomically:YES];
}
An NSDictionary is probably what you want. Then do a 'for' on it for each recipe / recipeDetail / wherever you're at in the structure?
You don't need to create "different arrays".
A plist is a textual (or binary) representation of a collection of objects. The valid object kinds come from a small set which includes NSArray, NSDictionary, NSNumber & NSString. The collection is rooted either in a dictionary or an array, each element of which can be any of the valid object kinds, including further dictionaries and arrays.
When you read the plist in your application the collection of objects is re-created. So if there are nested arrays or dictionaries they are recreated. To access them you just need to use the appropriate sequence of indices (arrays) and/or keys (dictionaries) which specify the element you need. You can store the returned object reference into a variable.
So for example if your plist is a dictionary keyed by vegetable names, each element of which is an array, and the third element of that array is another array of observed weights of that vegetable then you can access that array as follows (code just typed into answer, expect errors):
NSDictionary *vegetableInfo = [NSDictionary dictionaryWithContentsofURL:urlOfVegtableInfoPlist"];
NSArray *carrrotObservedWeights = vegetableInfo[#"Carrot"][3];
You now have a reference to the required array stored in carrrotObservedWeights.
If you are concerned over memory management, first use ARC. Second if you just want the extracted arrays and not the whole plist to be kept you just need to drop the reference to the plist after you've stored strong references to the contained arrays and ARC will clean up for you.
HTH
Addendum - After question clarified
First your plist is a dictionary of dictionaries where the containing dictionary has the keys Item 1, Item 2, etc. These keys carry no information and you would be better off making your plist an array of dictionaries.
Next we assume you have read in your plist using one of the standard methods and have a reference to it in sample - which is either an NSDictionary * if your plist is as shown, or an NSArray * if you modify the plist as suggested.
How many ways to do this? Many, here are three.
Method 1 - Simple Iteration
The straightforward way to obtain your arrays is simple iteration - iterate over each item and build your arrays. Here is a code fragment for two of the fields assuming the original dictionary of dictionaries:
NSMutableArray *foodNames = [NSMutableArray new];
NSMutableArray *hardnesses = [NSMutableArray new];
for (NSString *itemKey in sample) // for-in on a dictionary returns the keys
{
NSDictionary *item = sample[itemKey]; // use the key to obtain the contained dictionary
[foodNames addObject:item[#"foodName"]]; // extract each item
[hardnesses addObject:item[#"hardness"]];
}
The above fragment if sample is an array is similar.
Method 2 - Keypaths
If you do switch your plist to an array you can use a keypath to obtain all the values in one go. The method valueforkeypath: creates an array from an array by extracting the keypath - a list of keys separated by dots which allows for dictionaries within dictionaries. In your case the keypath has just one item:
NSMutableArray *foodNames = [sample valueForKeyPath:#"foodName"];
NSMutableArray *hardnesses = [sample valueForKeyPath:#"hardness"];
This will not work for your plist as shown with a top-level dictionary as they keypath is different each time - Item 1.foodName, Item 2.foodName, etc. - and wildcards (e.g. *.foodName) are not supported. A good reason to change your plist to have a top-level array!
Method 3 - Encapsulated Iteration
This is just a variation of method 1, but shows how to use the supplied block enumeration methods on NSDictionary. Rather than write a for loop yourself you can pass the body of the loop as a block to enumerateKeysAndObjectsUsingBlock: and it will perform the iteration:
NSMutableArray *foodNames = [NSMutableArray new];
NSMutableArray *hardnesses = [NSMutableArray new];
[sample enumerateKeysAndObjectsUsingBlock:^(id key, id item, BOOL *stop)
{
[foodNames addObject:item[#"foodName"]];
[hardnesses addObject:item[#"hardness"]];
}];
I'd make the root of the plist an array, but still have each item as a dictionary, so i could do something like this:
//ASSUMING YOUR PLIST IS IN RESOURCES FOLDER
NSString *filepath = [[NSBundle mainBundle] pathForResource:#"YOUR_PLIST_FILE_NAME" ofType:#"plist"];
NSArray *foodArray = [NSArray arrayWithContentsOfFile:filepath];
NSMutableArray *foodNames = [[NSMutable Array] init];
NSInteger i = 0;
for (Item *item in foodArray) {
NSDictionary *itemDict = [foodArray objectAtIndex:i];
NSString *foodName = [itemDict objectForKey:#"foodName"];
[foodNames addObject:foodName];
i++;
}
Hope this helps you figure something out!
This question already has answers here:
How do I sort an NSMutableArray with custom objects in it?
(27 answers)
Closed 9 years ago.
Hopefully someone can help.
I'm adding multiple objects to a NSMutableArray and I need to sort the order based on the first element which will always be a number.
However I'm unsure how to do this?
For example:
NSMutableArray *array = [[NSMutableArray alloc] init];
NSArray *object = [NSArray arrayWithObjects: #"1",#"Test",#"Test"];
[array addObject:object];
Thanks
If your array always contains other arrays, and the first element of the innermost array is always a string containing a number, you could use the NSMutableArray method sortUsingComparator to sort your array:
[array sortUsingComparator: ^(NSArray* obj1, NSArray* obj2)
{
int value1 = [obj1[0] integerValue];
int value2 = [obj2[0] integerValue];
if (value1==value2)
return NSOrderedSame;
else if (value1 < value2)
return NSOrderedAscending;
else
return NSOrderedDescending;
}
];
In the sortUsingComparator family of methods, you supply a block of code that the sort method uses to compare pairs of objects in your array. The block uses the standard typedef NSComparator, which takes 2 objects as parameters and returns a value of type NSComparisonResult.
The code above will probably crash if all the objects in your array are not arrays of strings. (Actually it would work if the first element of each component array was an NSNumber, since NSNumber also responds to the integerValue message.)
If you are going to use this code in a very controlled environment where you can be sure that the data you are sorting is well-formed, it should work as written. If there is any chance that the objects in the array would be of a different type, or be empty, or that their first element would not respond to the integerValue messages, then you should add error checking code.
If you sort your array alphanumerically, the object #"1" will appear before any words. Keep in mind though that #"1" in your code above is a string, not a number.
As to how to sort an array, look into [NSArray sortedArrayUsingComparator:] and similar methods.
I have a problem which I can't solve for a long time. I have a JSON response from the server which is parsed to NSDictionary lastMsgs as in the image below:
So for example 1323 it's a key and it associated with NSDictionary (which contains keys such as body, subject etc and values). So the problem I need in some way delete an entry which nested NSDictionary value has entry : type = 1. I don't know how to do this. I tried to do this:
NSMutableArray* _ModelVals = [[lastMsgs allValues] mutableCopy];
for (int i =0; i<[_ModelVals count]; i++) {
string_compare = [NSString stringWithFormat:#"%#" , [_ModelVals objectAtIndex:i]];
if ([string_compare rangeOfString:#"type = 1"].location != NSNotFound) {
[_ModelVals removeObjectAtIndex:i];
}
}
But it is work not correctly and delete not all entries which has type = 1. So the question - how can I implement this and delete entry in nested NSDictionary?
There is no value "type = 1" in the dictionary. That's just the log. You get the value of a key in a dictionary using [dict objectForKey:#"key"] or dict[#"key"].
Judging from your log, the type seems to be an NSNumber, not an NSString. Just get the int representation of it (assuming the type is an integer) and use a simple C int to int comparison.
And you can't filter an array like that. You will skip an entry. If you remove an entry, you have to decrease i by 1.
Or use this simpler solution:
NSSet *keys = [lastMsgs keysOfEntriesPassingTest:^BOOL(id key, id obj, BOOL *stop) {
return [obj[#"type"] intValue] == 1;
}];
NSMutableDictionary *dict = [lastMsgs mutableCopy];
[dict removeObjectsForKeys:[keys allObjects]];
This will first collect the keys of all objects (dictionaries) that have a type of 1 and then remove those from a mutable copy of the original dictionary.
You cannot add or remove objects from a collection while enumerating though it. I would create a another array that you can store references to the objects that you want to delete and remove them after you have looped though it.