Sorting array of objects using key in dictionary - ios

i have a Array of Dict , which display a Specific data into tableview ,I've been reading up on how to sort arrays of objects using the value of a property, and how to sort an array of NSDictionaries using one of their keys,
My Question is :-
how to customize header of Section in TableView and using category as a header section , see the screenshot i want to do the same thing .
(
{
categories = Protection;
"name_en" = "Wind deflectors, front";
"part_number" = A4221ADE00;
"part_pic1" = "a4221ade00/a4221ade00.jpg";
},
{
categories = Protection;
"name_en" = "Ice & sunscreen";
"part_number" = A4723ADE00;
"part_pic1" = "a4723ade00/a4723ade00.jpg";
},
{
categories = Protection;
"name_en" = "Carens-UN-BK-2009-AccBrochure";
"part_number" = "Carens-UN-BK-2009-AccBrochure";
"part_pic1" = "carens-un-bk-2009-accbrochure/carens-un-2009.jpg";
},
{
categories = Transport;
"name_en" = "Battery Petrol engines 2.0 GDI / Diesel engine 1.7 CRDi Carens";
"part_number" = LP370APE070CK0;
"part_pic1" = "b2143ade00ev/b2143ade00ev-soul2014-ev-floormat.jpg";
})

Not sure I entirely understand the question, but I'll take a stab. So the first step sounds like you want to sort the array of dictionaries based on the #"categories" key. This will put the array in alphabetical order based on that key:
NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:#"categories" ascending:YES];
NSArray *sortedArray = [arrayOfDictionaries sortedArrayUsingDescriptors:#[sortDescriptor]];
Then you want the section headers of the table to be the names of the categories. First you will need to find out how many different categories there are. A method like this can get that into an array without duplicates:
+ (NSArray *)findCategoriesInArray:(NSArray *)arrayOfDictionaries
{
NSMutableArray *categories = [NSMutableArray array];
for (id object in arrayOfDictionaries) {
if ([object isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = (NSDictionary *)object;
if (dictionary[#"categories"] && ![categories containsObject:dictionary[#"categories"]]) {
[categories addObject:dictionary[#"categories"]];
}
}
}
return categories;
}
At this point you have an array of all the dictionaries sorted by the categories key and an array containing the different categories. Let's assume the categories array from the method above gets assigned to a property self.categories (which is an NSArray). So now we can populate the section headers in the normal method calls:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return self.categories.count;
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return (NSString *)self.categories[section]; // I'm assuming these are all strings
}
Finally, we can populate the rows in each section:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSString *headerTitle = (NSString *)self.categories[section];
return [NameOfClass findNumberOfCategory:headerTitle inArray:self.arrayOfDictionaries];
}
+ (NSInteger)findNumberOfCategory:(NSString *)category inArray:(NSArray *)arrayOfDictionaries
{
NSInteger number = 0;
for (id object in arrayOfDictionaries) {
if ([object isKindOfClass:[NSDictionary class]]) {
NSDictionary *dictionary = (NSDictionary *)object;
if (dictionary[#"categories"] && [dictionary[#"categories"] isKindOfClass:[NSString class]]) {
NSString *thisCategory = (NSString *)dictionary[#"categories"];
if ([thisCategory isEqualToString:category]) {
number++;
}
}
}
}
return number;
}
Assuming both the self.arrayOfDictionaries and the self.categories arrays are both in alphabetical order, you can populate the cells themselves in tableView:cellForRowAtIndexPath: by getting the properties of the object from self.arrayOfDictionaries[indexPath.row].

Related

Finding distinct array elements based on dictionary key

I have two arrays of key-value pairs. Both these arrays contain different key-value pairs. I want to find elements in the first array that are not part of the second array based on a particular key.
Example:
1st Array - [{id=1, name="foo"},
{id=2, name="bar"}]
2nd Array - [{id=2, name="abc"},
{id=1, name="xyz"}]
Is there a way I can implement the same?
Right now I enumerate through the two arrays like so:
for (NSDictionary *eachPlayer in 1stArray) {
for (NSDictionary *eachPrediction in 2ndArray) {
if (eachPrediction[kId] != eachPlayer[kId]) {
[self.predictPlayerArray addObject:eachPlayer];
}
}
}
But this fails in the above case and adds both the values to the predictionPlayerArray - in the first iteration it adds 1 and in the forth iteration it adds 2. How do I prevent that from happening?
Thanks.
EDIT
I seem to have solved it this way. Not the best solution but it seems to be working:
for (NSDictionary *eachPlayer in arrayOne) {
for (NSDictionary *eachPrediction in arrayTwo) {
if (eachPrediction[kId] == eachPlayer[kId]) {
if ([self.predictPlayerArray containsObject:eachPlayer]) {
[self.predictPlayerArray removeObject:eachPlayer];
}
break;
}
else {
[self.predictPlayerArray addObject:eachPlayer];
}
self.predictPlayerArray = [self.predictPlayerArray valueForKeyPath:#"#distinctUnionOfObjects.self"];
}
}
Something like this should do:
NSArray *array1 = #[#{#"1":#"foo"},#{#"2":#"bar"},#{#"3":#"abc"}];
NSArray *array2 = #[#{#"2":#"abc"},#{#"1":#"abc"},#{#"4":#"foo"}];
NSMutableSet *result = [NSMutableSet new];
for (NSDictionary *dict1 in array1){
[dict1 enumerateKeysAndObjectsUsingBlock:^(id key1, id obj1, BOOL *stop1) {
for (NSDictionary *dict2 in array2) {
[dict2 enumerateKeysAndObjectsUsingBlock:^(id key2, id obj2, BOOL *stop2) {
if ([obj2 isEqual:obj1]){
[result addObject:#{key1:obj1}];
*stop2 = YES;
}
}];
}
}];
}
NSLog(#"result %#", result);
As you has nested dictionaries you should iterate also in them and finally store the result in a set that would prevent to have duplicate entries (if you use a NSMutableArray you will have twice {3:abc})
The log output is:
2015-02-03 13:53:07.897 test[19425:407184] result {(
{
1 = foo;
},
{
3 = abc;
}
)}

How to get rid of specific TableView Cells

My tableview is populated with cells that show an item with its name, price, and brand. Some objects that I get from the web service return , and that looks ugly on the table view cell. I don't want to populate table view cells that have a price of "null". Here is my code so far. For now, I change it to say "Price Unavailable".
for (NSDictionary *objItem in resultsArray)
{
NSString *currentItemName = [objectTitlesArray objectAtIndex:indexPath.row];
if ([currentItemName isEqualToString:objItem[#"title"]])
{
if (cell.priceLabel.text != (id)[NSNull null] && cell.priceLabel.text.length != 0 && cell.brandLabel.text != (id)[NSNull null] && cell.brandLabel.text.length != 0)
{
cell.nameLabel.text = currentItemName;
NSDictionary *bestPageDictionary = objItem[#"best_page"];
NSNumber *price = bestPageDictionary[#"price"];
if ((cell.priceLabel.text = #"<null>"))
{
cell.priceLabel.text = #"Price Unavailable";
}
else
{
cell.priceLabel.text = [NSString stringWithFormat:#"$%#", price];
}
NSArray *brandsArray = objItem[#"brands"];
cell.brandLabel.text = [brandsArray firstObject];
}
}
}
This is terribly inefficient. You are keeping the JSON (I'm assuming) return in a dictionary, then looping over the dictionary for EVERY cell that you're creating. Not only that, you aren't cleaning up the JSON ahead of time.
It's much more expensive to create a useless cell then go back and try to delete it. In your numberOfRowsInSection delegate, you've already told the tableview that you have X many cells. Now you're trying to delete the cells which will mess up the callbacks. You'd have to create a method that will run after you finish creating all cells that will then loop through all your cells to delete them from the tableView, which will then call [table reloadData]. However, because you actually aren't removing the data from the NSDictionary, you actually will create the same amount of cells again and be stuck in an infinite loop.
Solution:
First, change your structure. Once you get the JSON return, sanitize it to delete all values with no price. I suggest also using an object class to hold each server object. This will simplify your code a lot for the TableView as well as other classes as well. Now that you've cleaned up your return, change it from a NSDictionary into a NSMutableArray. Then in numberOfRowsInSection: you call [array count]. In cellForRowAtIndexPath: you simply look at [array objectAtIndex:indexPath.row] to get your object.
You'll want code to generally look like:
ServerItem.h : NSObject{
#property (nonatomic,retain) NSString* name;
...
add in other properties here
}
- (NSMutableArray *) parseJSON:(NSDictionary *)jsonDict{
NSMutableArray *returnArray = [NSMutableArray array];
NSArray *dictArray = [NSArray arrayWithArray:jsonDict[#"results"]];
for (NSDictionary *itemDict in dictArray)
{
NSDictionary *bestPageDictionary = objItem[#"best_page"];
if (![bestPageDictionary[#"price"] isEqualToString:#"<null>"]])
{
ServerItem item = [ServerItem new];
item.price = ...
item.name = ....
[returnArray addObject:item];
}
}
return returnArray;
}
Inside your webService code:
self.itemArray = [self parseJSON:dataDictionary];
Then
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
return [self.itemCallArray count]
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
ServerItem *cellItem = [self.itemsArray objectAtIndex:indexPath.row];
cell.nameLabel.text = cellItem.name;
cell.priceLabel.text = cellItem.price;
...
}

Create NSDictionary for calculating numberOfRowsInSection

I am trying to display sections and rows correctly for my uiTableView.
I have had great help from one contributor and am fairly close to fixing my issue. The Issue can be seen here. Its not far off being right, its just the sections that need to be sorted.
It is repeating the section titles instead of only showing it once. Im not sure exactly how to fix this.
// Find out the path of recipes.plist
NSString *path = [[NSBundle mainBundle] pathForResource:#"lawpolice" ofType:#"plist"];
// Load the file content and read the data into arrays
self.dataArray = [NSArray arrayWithContentsOfFile:path];
//Sort the array by section
self.sortedArray = [self.dataArray sortedArrayUsingDescriptors:#[
[NSSortDescriptor sortDescriptorWithKey:#"Section" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:#"Title" ascending:YES]]];
self.temp = [[NSMutableDictionary alloc] init];
for (NSDictionary *dict in self.sortedArray) {
NSMutableArray *array = self.temp[dict[#"Section"]];
// No items with the same section key stored yet, so we need to initialize a new array.
if (array == NULL) {
array = [[NSMutableArray alloc] init];
}
// Store the title in the array.
[array addObject:dict[#"Title"]];
// Save the array as the value for the section key.
[self.temp setObject:array forKey:dict[#"Section"]];
}
self.policePowers = [self.temp copy]; // copy returns an immutable copy of temp.
//Section for sorting
self.sectionArray = [self.sortedArray valueForKeyPath:#"Section"];
NSLog(#"%#", self.sectionArray);
//Title
self.namesArray = [self.sortedArray valueForKeyPath:#"Title"];
//Offence
self.offenseArray = [self.sortedArray valueForKeyPath:#"Offence"];
//Points to Prove
self.ptpArray = [self.sortedArray valueForKeyPath:#"PTP"];
//Action
self.actionsArray = [self.sortedArray valueForKeyPath:#"Actions"];
//Notes
self.notesArray = [self.sortedArray valueForKeyPath:#"Notes"];
//Legislation
self.legislationArray = [self.sortedArray valueForKeyPath:#"Legislation"];
//PNLD
self.pnldArray = [self.sortedArray valueForKeyPath:#"PNLD"];
//Image
self.imageString = [self.sortedArray valueForKeyPath:#"image"];
titleForHeaderInSection
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
return [self.sectionArray objectAtIndex:section];
}
numberOfSectionsInTableView
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.policePowers count];
}
numberOfRowsInSection
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *sectionrows = self.policePowers[self.sectionArray[section]];
return [sectionrows count];
}
Update
To be clear, if two items have the same Section value, I want to automatically group them into an array and have that array mapped to the Section value at the end
NSDictionary dictionaryWithObjects:forKeys: basically loops through two arrays and maps the object in one array at the current index as the key for the object in the other array at the same index. When you're calling
self.policePowers = [NSDictionary dictionaryWithObjects:self.namesArray forKeys:self.sectionArray];
it therefore maps the items in self.sectionArray as the keys for the items in self.namesArray. Looking at your plist file, the "Title" keypath (which is mapped to self.namesArray) has a value of string, so your NSLog results make sense, as self.namesArray is an array of strings, not an array of arrays.
I'm not sure how you were supposed to get a result like
"Alcohol: Licensing/Drive unfit" = {
"Drive/attempt to drive/in charge whilst unfit or over",
"Drive/attempt to drive/in charge whilst unfit or over",
"Drive/attempt to drive/in charge whilst unfit or over",
}
Where is that array supposed to come from?
-- EDIT --
I don't think there's a concise way to accomplish what you want, so it'd have to be done manually. I haven't actually used [NSArray arrayWithContentsOfFile:path] before, so is self.dataArray an array of dictionaries with each item representing one of the items in the plist (Item 44, Item 45, etc)? If so, you could do something like this:
NSMutableDictionary *temp = [[NSMutableDictionary alloc] init];
for (NSDictionary *dict in self.dataArray) {
NSMutableArray *array = temp[dict[#"Section"]];
// No items with the same section key stored yet, so we need to initialize a new array.
if (array == null) {
array = [[NSMutableArray alloc] init];
}
// Store the title in the array.
[array addObject:dict[#"Title"]];
// Save the array as the value for the section key.
[temp setObject:array forKey:dict[#"Section"]];
}
self.policePowers = [temp copy]; // copy returns an immutable copy of temp.
-- EDIT AGAIN --
The app crashes because self.policePowers is an NSDictionary, not an NSArray; thus it doesn't have an objectAtIndex: method. If you're trying to get the section title, try this instead:
return [self.sectionArray objectAtIndex:section];
Furthermore, if you're working with a table view, I'd basically have self.sectionArray sorted whichever way you like, then whenever I needed to populate data in each section, I would use self.policePowers[self.sectionArray[section]] to return the array of titles mapped to that section title.
-- YET ANOTHER --
If you break it up into the following lines, where is the NSRangeException thrown? If you NSLog, do the results match what you expect?
NSString *title = self.sortedKeys[indexPath.section];
NSArray *array = self.policePowers[title];
NSString *value = array[indexPath.row];

ios - convert NSArray to NSDictionary

i want to convert an nsarray to nsdictionary i'm using to
- (NSDictionary *) indexKeyedDictionaryFromArray:(NSArray *)array
{
id objectInstance;
NSUInteger indexKey = 0;
NSMutableDictionary *mutableDictionary = [[NSMutableDictionary alloc] init];
for (objectInstance in array)
[mutableDictionary setObject:objectInstance forKey:[NSNumber numberWithUnsignedInt:indexKey++]];
return (NSDictionary *)[mutableDictionary autorelease];
}
output result is:
{
0 = {
Event = "";
ID = 1; };
3 = {
Event = "";
ID = 77; };
2 = {
Event = "";
ID = 23; };
1 = {
Event = "";
ID = 45; };
7 = {
Event = "";
ID = 10; };
5 = {
Event = "";
ID = 26; };
6 = {
Event = "";
ID = 27;
};
8 = {
Event = "";
ID = 28;
};
}
After convert to nsdictionary, the order of nsdictionary isn't true to the original order, i want to display the same order in nsarray, i don't know how? can you help me?
NSDictionary does not have an order. Sort the keys and use them to access the entries.
If I understand correctly from your responses to #ACB and #Zaph in the comments, you want to do the following:
Maintain a collection mapping integer keys to object values which is ordered by the keys.
If I'm understanding correctly, an array won't be good enough for your purposes because the integer keys in an array allow for no "holes". You, however, need to allow for holes: in the output in your question, the key-value pair for 4 is missing. For this reason, a dictionary is appealing to you.
Unfortunately, a dictionary will not allow you to maintain an ordering on the key-value pairs it contains, as #Zaph points out. You say, however, you just want to display the values in the dictionary ordered by the keys in a UITableView. Presumably, it is unimportant the order in which the dictionary is serialized to disk (using writeToFile:atomically:) so long as the contents of the dictionary are displayed in the correct order in the table view.
A dictionary can be used for this purpose as follows. First, we'll need a class PFXKeyValuePair;
#interface PFXKeyValuePair : NSObject
#property (nonatomic) id<NSCopying> key;
#property (nonatomic) id value;
+ (PFXKeyValuePair *)pairWithValue:(id)value forKey:(id<NSCopying>)key;
+ (NSArray *)pairsWithValues:(NSArray *)values forKeys:(NSArray *)keys;
#end
#implementation PFXKeyValuePair
+ (PFXKeyValuePair *)pairWithValue:(id)value forKey:(id<NSCopying>)key
{
PFXKeyValuePair *pair = [[PFXKeyValuePair alloc] init];
pair.value = value;
pair.key = key;
return pair;
}
+ (NSArray *)pairsWithValues:(NSArray *)values forKeys:(NSArray *)keys
{
NSAssert(values.count == keys.count, #"The array of values must be the same size as the array of keys.");
NSUInteger count = values.count;
NSMutableArray *mutableRes = [NSMutableArray array];
for (NSUInteger index = 0; index < count; index++) {
PFXKeyValuePair *pair = [PFXKeyValuePair pairWithValue:values[index] forKey:keys[index]];
[mutableRes addObject:pair];
}
return [mutableRes copy];
}
#end
Second, we'll need a category method on NSDictionary:
#interface NSDictionary (PFXAdditions)
- (NSArray *)pfx_keyValuePairsSortedByKeyUsingComparator:(NSComparator)comparator;
#end
#implementation NSDictionary (PFXAdditions)
- (NSArray *)pfx_keyValuePairsSortedByKeyUsingComparator:(NSComparator)comparator
{
NSArray *sortedKeys = [self.allKeys sortedArrayUsingComparator:comparator];
NSArray *sortedValues = [self objectsForKeys:sortedKeys notFoundMarker:[NSNull null]];
return [PFXKeyValuePair pairsWithValues:sortedValues forKeys:sortedKeys];
}
#end
Note: In the above, PFX and pfx are placeholders. You ought to replace them with prefixes appropriate to your project.
We can then use this category method when to populate our UITableView. Let's say we have a property
#property (nonatomic) NSDictionary *events;
And let's assume that the table view has only one section in which these events will be shown.
Then we can implement –tableView:numberOfRowsInSection: in our UITableViewController subclass as follows:
– (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return self.events.count;
}
And within our implementation of –tableView:numberOfRowsInSection: we can determine the appropriate entry in the dictionary to use as follows:
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
//...
NSArray *pairs = [self.events pfx_keyValuePairsSortedByKeyUsingComparator:^(id obj1, id obj2) {
NSNumber *key1 = (NSNumber *)obj1;
NSNumber *key2 = (NSNumber *)obj2;
return [key1 compare:key2];
}];
NSUInteger index = [indexPath indexAtPosition:1];
PFXKeyValuePair *pair = pairs[index];
/*
At this point, pair.value will be a dictionary as in your output above
holding a value for the key #"Event" and a value for the key #"ID".
*/
//...
}
This could be made faster by making pairs a property and only computing it when necessary (for example, by only computing pairs just prior to reloading the table's data).
Note: Using this approach, the dictionary will still not be serialized to disk (when calling -writeToDisk:atomically:) "in order" and your output will still look the same as in your question. However, this does not matter: when the data is displayed to the user in the table view, the data will be ordered as you're hoping.
This is example one of the exmple get the emplyee list NSMutableArray and create NSMutableDictionary.......
NSMutableArray *emloyees = [[NSMutableArray alloc]initWithObjects:#"saman",#"Ruchira",#"Rukshan",#"ishan",#"Harsha",#"Ghihan",#"Lakmali",#"Dasuni", nil];
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
for (NSString *word in emloyees) {
NSString *firstLetter = [[word substringToIndex:1] uppercaseString];
letterList = [dict objectForKey:firstLetter];
if (!letterList) {
letterList = [NSMutableArray array];
[dict setObject:letterList forKey:firstLetter];
}
[letterList addObject:word];}NSLog(#"dic %#",dict);

Alphabetic TableView with Three20

I have a simple TTTableViewController to represent a set of companies. I would like to sort the TableView alphabetically using sections and the letter selector on the right side of the TableView.
Is there an easy way to do this using Three20?
Currently I don't have any separate datasource.
- (void)createModel {
NSMutableArray* itemsArray = [[NSMutableArray alloc] init];
for(IDCompany* company in companies) {
[itemsArray addObject:[TTTableSubtitleItem itemWithText:[company title] subtitle:[company companyDescription] URL:[company urlToDetailView]]];
}
self.dataSource = [TTListDataSource dataSourceWithItems:itemsArray];
[itemsArray release];
}
for starters you should use TTSectionedDataSource. It supports sections by having 2 NSMutableArray - one for sections represented by an array of strings and the other by array of arrays with the items of the table for each section.
Getting the letters is pretty simple too. UITableViewDataSource supports:
- (NSArray*)sectionIndexTitlesForTableView:(UITableView*)tableView;
and the base class in three20 supports extracting them by doing this:
- (NSArray*)sectionIndexTitlesForTableView:(UITableView*)tableView {
return [TTTableViewDataSource lettersForSectionsWithSearch:YES summary:NO];
}
The best solution for you would be to create a new DataSource and inherit it from TTSectionedDataSource, then implement something like this to build the sections:
self.items = [NSMutableArray array];
self.sections = [NSMutableArray array];
NSMutableDictionary* groups = [NSMutableDictionary dictionary];
for (NSString* name in _addressBook.names) {
NSString* letter = [NSString stringWithFormat:#"%C", [name characterAtIndex:0]];
NSMutableArray* section = [groups objectForKey:letter];
if (!section) {
section = [NSMutableArray array];
[groups setObject:section forKey:letter];
}
TTTableItem* item = [TTTableTextItem itemWithText:name URL:nil];
[section addObject:item];
}
NSArray* letters = [groups.allKeys sortedArrayUsingSelector:#selector(caseInsensitiveCompare:)];
for (NSString* letter in letters) {
NSArray* items = [groups objectForKey:letter];
[_sections addObject:letter];
[_items addObject:items];
}
For a complete working solution refer to the TTCatalog samples under the three20 source and in there you will find MockDataSource.m that has this code.

Resources