Storing Multiple Values to a Single Key in NSDictionary - ios

In my application i am getting data from the server.i parsed the data and added to individual arrays. Here i am having 2 arrays.
For example
Array A : #"1",#"2",#"3",#"2",#"3",#"4",etc..
Array B : #"A",#"B",#"C",#"D",#"E",#"F",etc..
Now i want to create a Dictionary with Array A as keys and Array B as Values.
i am trying to create Dictionary like this:
dataDict = [NSDictionary dictionaryWithObjects:B forKeys:A];
But it is giving only single value for a single Key. here how can i store multiple values for a single key.

For Different keys its working. But my problem is Storing multiple values for single key.
You can't store multiple values for a single key directly -- dictionaries can only have one value per key. What you can do is store an array as the value. So, you could create a mutable dictionary and add the keys and values one at a time. Make the values all mutable arrays, and check for an existing value for the given key before setting it. If you find one, add the new value to the array.

Try this,
Assuming dataDict is a NSMutableDictionary and initialised.
- (void)addValueInDataDict:(id)value forKey:(NSString *)key {
if ([dataDict objectForKey:key] != nil) {
//Already exist a value for the key
id object = [dataDict objectForKey:key];
NSMutableArray *objectArray;
if ([object isKindOfClass:[NSMutableArray class]]) {
objectArray = (NSMutableArray *)object;
} else {
NSMutableArray *objectArray = [[NSMutableArray alloc] init];
}
[objectArray addObject:value];
[dataDict setObject:objectArray forKey:key];
} else {
//No value for the key
[dataDict setObject:value forKey:key];
}
}

Related

Add object to array within NSMutableDictionary loaded from NSUserDefaults

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]]]

How to populate an array with dictionaries containing certain key/values

I'm working with a plist file at the moment but intend to switch over to json when the backend is finally built. So for the moment my plist is an array that contains a bunch of dictionaries.
I'd like to use this information to create a new array containing only the dictionaries with certain values.
For example. My plist contains a bunch of locations like so:
key: location value:example place name here
key: type value:indoor
I want to build an array containing only those with "indoor" set as the type value.
And then perhaps a second one containing all "outdoor" locations.
What's the best way to go about doing this, or perhaps I can be directed to a tutorial of some sort.
Thanks.
Simply loop through your array and add the qualifying dictionaries to a new array.
NSMutableArray *arrayIndoor = [NSMutableArray array];
NSMutableArray *arrayOutdoor = [NSMutableArray array];
NSString *type;
for (NSDictionary *dict in arrayPList) {
type = [dict objectForKey:#"type"];
if ([type isEqualToString:#"indoor"])
[arrayIndoor addObject:dict];
else if ([type isEqualToString:#"indoor"])
[arrayOutdoor addObject:dict];
}
All you are really needing to do is sort the array into two arrays. There isn't a direct method that I have seen that will do this for you. My suggestion would be to use a fast enumeration over the array and conditionally break it into two new arrays.
NSMutableArray *locations = [[NSMutableArray alloc] init];
NSMutableArray *type = [[NSMutableArray alloc] init];
for (NSDictionary *dict in MyPlistArray) {
if ([dict valueForKey:#"locationKey"]) {
[locations addObject:dict];
} else if ([dict valueForKey:#"typeKey"]) {
[type addObject:dict];
}
}
You might need to use a different method for determining which key to put in each array, but you get the general idea.
Also I'm assuming that you would want the arrays of dictionaries to persist after, so you can just set those up as properties instead of local variables.

With fast enumeration and an NSDictionary, iterating in the order of the keys is not guaranteed – how can I make it so it IS in order?

I'm communicating with an API that sends back an NSDictionary as a response with data my app needs (the data is basically a feed). This data is sorted by newest to oldest, with the newest items at the front of the NSDictionary.
When I fast enumerate through them with for (NSString *key in articles) { ... } the order is seemingly random, and thus the order I operate on them isn't in order from newest to oldest, like I want it to be, but completely random instead.
I've read up, and when using fast enumeration with NSDictionary it is not guaranteed to iterate in order through the array.
However, I need it to. How do I make it iterate through the NSDictionary in the order that NSDictionary is in?
One way could be to get all keys in a mutable array:
NSMutableArray *allKeys = [[dictionary allKeys] mutableCopy];
And then sort the array to your needs:
[allKeys sortUsingComparator: ....,]; //or another sorting method
You can then iterate over the array (using fast enumeration here keeps the order, I think), and get the dictionary values for the current key:
for (NSString *key in allKeys) {
id object = [dictionary objectForKey: key];
//do your thing with the object
}
Dictionaries are, by definition, unordered. If you want to apply an order to the keys, you need to sort the keys.
NSArray *keys = [articles allKeys];
NSArray *sortedKeys = [keys sortedArrayUsingSelector:#selector(compare:)];
for (NSString *key in sortedKeys) {
// process key
}
Update the way the keys are sorted to suit your needs.
As other people said, you cannot garantee order in NSDictionary. And sometimes ordering the allKeys property it's not what you really want. If what you really want is enumerate your dict by the order your keys were inserted in your dict, you can create a new NSMutableArray property/variable to store your keys, so they will preserve its order.
Everytime you will insert a new key in the dict, insert it to in your array:
[articles addObject:someArticle forKey:#"article1"];
[self.keys addObject:#"article1"];
To enumerate them in order, just do:
for (NSString *key in self.keys) {
id object = articles[key];
}

Merging dictionaries - Incompatible type error

I've been trying to merge two NSDictionaries for a couple hours now. Searched and found that I can use [NSMutableDictionary addEntriesFromDictionary:].
NSDictionary *areaAttributes = [[area entity] attributesByName];
NSDictionary *gpsAttributes = [[gps entity] attributesByName];
NSMutableDictionary *areaAttributesM = [areaAttributes mutableCopy];
NSMutableDictionary *gpsAttributesM = [gpsAttributes mutableCopy];
NSMutableDictionary *combinedAttributes = [areaAttributesM addEntriesFromDictionary:gpsAttributesM];
But I get the error:
Initializing 'NSMutableDictionary *_strong' with an expression of incompatible type 'void'
So this is saying that [areaAttributesM addEntriesFromDictionary:gpsAttributesM] returns void? Is my understanding correct? And why is it returning void?
Yes, you are correct. From the docs:
- (void)addEntriesFromDictionary:(NSDictionary *)otherDictionary
As to why, that's simple: Functions that mutate an object in place in Cocoa usually return void, so you can easily distinguish them from functions that return a different object.
Also, there's no reason to mutableCopy the gpsAttributes dictionary; it's just being used as the argument to -[addEntriesFromDictionary:], which doesn't need to be mutable.
So, the right way to do this is:
NSDictionary *areaAttributes = [[area entity] attributesByName];
NSDictionary *gpsAttributes = [[gps entity] attributesByName];
NSMutableDictionary *combinedAttributes = [areaAttributes mutableCopy];
[combinedAttributes addEntriesFromDictionary:gpsAttributes];
You may want to wrap this up in a function (or a method in a category on NSDictionary), if you do if often:
NSDictionary *mergeDictionaries(NSDictionary *lhs, NSDictionary *rhs) {
NSMutableDictionary *ret = [lhs mutableCopy];
[ret addEntriesFromDictionary:rhs];
return ret;
}
From the Documentation, addEntriesFromDictionary tells that:
If both dictionaries contain the same key, the receiving dictionary’s previous value object for that key is sent a release message, and the new value object takes its place.
You need to use setObject to add each object to the dictionary.YOu need to loop through the keys of one dictionary and add it to the final dictionary.
Even setObject tells the same:
The key for value. The key is copied (using copyWithZone:; keys must conform to the NSCopying protocol). If aKey already exists in the dictionary, anObject takes its place.
You cannot have two same keys in the dictionary. All keys in the dictionary are unique.
If you still want to have the same key-value in the dictionary, you must use a different key.
For example, you have two dictionaries with following values:
NSDictionary *dict1=#{#"hello":#"1",#"hello2" :#"2"};
NSDictionary *dict2=#{#"hello":#"1",#"hello2":#"2",#"hello3":#"1",#"hello6":#"2",#"hello4":#"1",#"hello5" :#"2"};
NSMutableDictionary *mutableDict=[NSMutableDictionary dictionaryWithDictionary:dict1];
for (id key in dict2.allKeys){
for (id subKey in dict1.allKeys){
if (key==subKey) {
[mutableDict setObject:dict2[key] forKey:[NSString stringWithFormat:#"Ext-%#",key]];
}else{
[mutableDict setObject:dict2[key] forKey:key];
}
}
}
and by the end of this loop, your new mutable dictionaries will have the follwoing key-values:
{
"Ext-hello" = 1;
"Ext-hello2" = 2;
hello = 1;
hello2 = 2;
hello3 = 1;
hello4 = 1;
hello5 = 2;
hello6 = 2;
}
As you can see, hello, and hello2 keys are renamed as Ext-hello1, Ext-hello2. form the dict1, and you still have all the dict2 values added to your mutable dict.
IF you don't want to add a new key, then you can add the values into an arrya and add that array to the dictionary. YOu can modify the for-loop to:
for (id key in dict2.allKeys){
for (id subKey in dict1.allKeys){
if (key==subKey) {
NSMutableArray *myArr=[[NSMutableArray alloc]init];
[myArr addObject:dict1[subKey]];
[myArr addObject:dict2[key]];
[mutableDict setObject:myArr forKey:key];
}else{
[mutableDict setObject:dict2[key] forKey:key];
}
}
}
And now you will have the values merged into an array:
{
hello = (
1,
1
);
hello2 = 2;
hello3 = 1;
hello4 = 1;
hello5 = 2;
hello6 = 2;
}
In this way, the number of keys will be same, and the values for the same key will be added as an array.

How to delete an entry from NSDictionary which is the element of NSDictionary?

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.

Resources