Spilt an NSMutableArray of NSDictionaries - ios

I am trying to create a view that contains a UITableView, I would like to populate the UITableView using an NSMutableArray of NSDictionaries. I would however like to split the NSMutableArray into sections using one of the NSDictionaries key values.
The NSDictionary looks like this
First Name
Last Name
Age
The NSMutableArray is not sorted however I would like to create the UITableView that is split up into sections based on age and sorted lowest to highest.

NSArray has a method called sortedArrayUsingComparator:, which is called on an NSArray object and takes a block which returns as NSComparison result as an argument.
You can sort on the Age key as such:
NSArray *sortedArray = [yourArray sortedArrayUsingComparator:
^NSComparisonResult(id a, id b) {
NSInteger ageA = [[(NSDictionary*)a objectForKey:#"Age"] integerValue];
NSInteger ageB = [[(NSDictionary*)b objectForKey:#"Age"] integerValue];
if(a < b)
return NSOrderedAscending;
if(b < a)
return NSOrderedDescending;
else
return NSOrderedSame;
}];
For splitting into sections, since we've already sorted it, we can just iterate through the array:
NSMutableArray *sectionedArray = [NSMutableArray array];
for(NSDictionary *curDict in sortedArray) {
if([[curDict objectForKey:#"Age"] isEqual:
[[[sectionedArray lastObject] firstObject] objectForKey:#"Age"]) {
[[sectionedArray lastObject] addObject:curDict];
} else {
NSMutableArray *newSection = [NSMutableArray arrayWithObject:curDict];
[sectionedArray addObject:newSection];
}
}

First of all, you need to sort the array with this function:
self.mutableArray = [mutableArray sortedArrayUsingComparator:^NSComparisonResult(id a, id b) {
NSInteger ageA = [[(NSDictionary*)a objectForKey:#"Age"] integerValue];
NSInteger ageB = [[(NSDictionary*)b objectForKey:#"Age"] integerValue];
if(a < b) return NSOrderedAscending;
if(b < a) return NSOrderedDescending;
return NSOrderedSame;
}];
Now you can extract from the array the distinct values for the key "Age":
NSArray *uniqueAges = [_mutableArray valueForKeyPath:#"#distinctUnionOfObjects.Age"];
Finally, you can use this array to return the number of section in the tableView delegate:
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [uniqueAges count];
}
And also you can use the array to return the name of the section:
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
return [uniqueAges objectAtIndex:section];
//remember to return a string if isn't so!
}

Related

Make an array equal to another array for specific objects

What I'm trying to do is to make an array equal to another but for specific objects. Like the second array holds a lot of objects and I want the first array to take specific objects from the second array and then return it in NumberOfRowsInSection.
Here is what I tried but it returns nothing:
-(NSInteger) tableView: (UITableView * ) tableView numberOfRowsInSection: (NSINteger) section {
NSArray *array;
NSString * foundString;
NSInteger i;
for (i = 0; i < [LabelFilesArray count]; i++) {
if ([[[LabelFilesArray objectAtIndex: i] objectForKey: #"teamSide"] isEqualToString: #"test2"]) {
array = [LabelFilesArray objectAtIndex: i] ObjectForKey: #"teamSide"]; //Here is my problem.
foundString = #"found";
if ([foundString isEqualToString: #"found"]) {
break;
}
}
}
return array.count; //it returns nothing
}
You need to keep things simple, so select the data you want in your tableview as soon as you get it, and store it in a 3rd array.
This allows you to delete and re-arrange rows and makes your datasource methods simple.
ViewController.m:
#interface ViewController()
{
NSMutableArray *_tableData;
}
- (void)methodThatGetsData
{
// Get data in LabelFilesArray
_tableData = [NSMutableArray new];
for (NSDictionary *dict in LabelFilesArray) {
for (NSString *filter in _filterArray) {
if (dict[#"teamSide"] isEqualToString:filter]) {
[_tableData addObject:dict];
break;
}
}
}
[_tableView reloadData];
}
-(NSInteger) tableView:(UITableView *)tableView numberOfRowsInSection: (NSINteger)section {
[_tableData count];
}
// etc.
You can use NSPredicate also to filter contents of array.
Example:
NSPredicate *predicate = [NSPredicate predicateWithFormat:#"%K = %#", key, [dict objectForKey:key]];
_filterArray = [LabelFilesArray filteredArrayUsingPredicate:predicate];

How do i get numberOfRowsInSection from nested NSMutableDictionary?

I am trying to follow this post http://www.appcoda.com/ios-programming-index-list-uitableview/ to create sections using UITableViewController.
I am using two NSMutableDictionaries first(_curatedItemDictionary) dictionary is to store key value pairs from Parse and second (_sectionsDictionary) is to store these in dictionary as key and values as Category type.
[_sectionsDictionary setObject:[obj objectForKey:#"Category"] forKey:_curatedItemDictionary];
I did this because i couldn't store the key as category and value as dictionary as dictionaries will not support duplicate keys.
I am getting numberOfSectionsInTableView by using the below code
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
// Return the number of sections.
for(NSString *key in [_sectionsDictionary allKeys]) {
[_cleanArray addObject:[_sectionsDictionary objectForKey:key]];
}
NSCountedSet *set = [[NSCountedSet alloc] initWithArray:_cleanArray];
for (id item in set) {
NSInteger count = (unsigned long)[set countForObject:item];
[_rowForIndexArray addObject:[NSNumber numberWithInteger:count]];
}
_cleanSectionArray = [[NSSet setWithArray:_cleanArray] allObjects];
return [_cleanSectionArray count];
}
How do i get numberOfRowsInSection from the above information ? How do i know which items are being iterated for a particular section ?
Here is the code I am using to create curatedItemDictionary from Parse.com data
PFQuery *query = [PFQuery queryWithClassName:[NSString stringWithString:self.classname]];
[query findObjectsInBackgroundWithBlock:^(NSArray *objects, NSError *error) {
if (!error) {
// The find succeeded.
for (PFObject *obj in objects) {
self.curatedItemDictionary = [[NSMutableDictionary alloc]init];
[_curatedItemDictionary setValue:[obj objectForKey:#"ItemName"] forKey:#"ItemName"];
[_curatedItemDictionary setValue:[obj objectForKey:#"Price"] forKey:#"Price"];
[_sectionsDictionary setObject:[obj objectForKey:#"Category"] forKey:_curatedItemDictionary];
[self.tableView reloadData];
} else {
// Log details of the failure
NSLog(#"Error: %# %#", error, [error userInfo]);
}
}];
Based on the code provided in the comment to the original post something similar to that, just an alternative:
self.sectionsDictionary = [NSMutableDictionary new];
NSMutableArray *sectionsArray;
for (PFObject *obj in objects) {
sectionsArray = [self.sectionsDictionary objectForKey: [obj objectForKey:#"Category"]];
if (nil == sectionsArray) sectionsArray = [NSMutableArray new];
[sectionsArray addObject: obj]
[self.sectionsDictionary setObject: sectionsArray forKey: [obj objectForKey:#"Category"]];
}
Then in your numberOfSectionsInTableView you can get the amount of sections just by accessing count method on NSMutableDictionary
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
return [self.sectionsDictionary count];
}
And then if you need number of rows in each particular section:
- (NSInteger) tableView:(UITableView*)tableView numberOfRowsInSection:(NSInteger)section
{
NSArray *keys = [self.sectionsDictionary allKeys];
id aKey = [keys objectAtIndex:section];
NSMutableArray *arr = [self.sectionsDictionary objectForKey:aKey];
return (arr) ? arr.count : 0;
}
But of course you can loop through the keys in your sectionsDictionary when preparing the datasource and put section index with some key, so you do not need to do use the allKeys trick.

How can I select elements from an NSArray according to array of NSIndexPaths

Is there a fast way to get all elements at indexes from an array returned from a UITableView (NSArray of NSIndexPaths).
For instance:
[self.dataSourceArray selectItemsAt:[self.tableView indexPathsForSelectedItems]]
There is no built-in method, but you can simply loop over the selected rows
(assuming that there is only one section) and add the corresponding elements
to a mutable array:
NSMutableArray *selectedObjects = [NSMutableArray array];
for (NSIndexPath *indexPath in [self.tableView indexPathsForSelectedRows]) {
[selectedObjects addObject:self.dataSourceArray[indexPath.row]];
}
word you are looking for is Lambda
there is two methots for that
you can look for it in link
Does Objective-C have List Lambda Query like C#?
it should look like this
NSPredicate *p = [NSPredicate predicateWithBlock:^BOOL(id evaluatedObject, NSDictionary *bindings) {
for(NSIndexPath i in [self.tableView indexPathsForSelectedItems]){
if(i.row == evaluatedObject.row){
return YES;
}
}
return NO;
}];
NSArray *result = [self.dataSourceArray filteredArrayUsingPredicate:p];
Use objectsAtIndexes: method of NSArray.
NSMutableIndexSet *mutableIndexSet = [[NSMutableIndexSet alloc] init];
// Add all indexes to NSMutableIndexSet
for (int i = 0; i < [self.tableView indexPathsForSelectedRows].count; i++) {
[mutableIndexSet addIndex: ((NSIndexPath *) [self.tableView indexPathsForSelectedRows][i]).row];
}
[self.dataSourceArray objectsAtIndexes:mutableIndexSet];

How to show an index that looks like the iPod music with # for non english songs

I would like to show an index bar on my tableview with all my songs sorted by alphabetical order and those foreign language songs and numerical in # just like how the iPod music in iOS.
I read the first character of all my song array object exclude duplicates and append it as the index.
How can i filter foreign / non alphabet and numbers ?
this is how it looks like, ugly.
would to show something like iPod.
-(NSMutableArray *)updateSongSectionIndexWithArray:(NSArray*)songArray andSelector:(SEL)selector
{
NSArray *indexedArray = [self partitionObjects:songArray collationStringSelector:selector];
return [[NSMutableArray alloc]initWithArray:indexedArray];
}
-(NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector
{
self.collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionCount = [[self.collation sectionTitles] count];//section count is take from sectionTitles and not sectionIndexTitles
NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];
//create an array to hold the data for each section
for(int i = 0; i < sectionCount; i++)
{
[unsortedSections addObject:[NSMutableArray array]];
}
if ([self.catString isEqualToString:ARTISTS])
{
//put each object into a section
for (id object in array)
{
if (!object)
{
continue;
}
NSInteger index = [self.collation sectionForObject:object collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
}
else
{
for (id object in array)
{
NSInteger index = [self.collation sectionForObject:object collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
}
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
//sort each section
for (NSMutableArray *section in unsortedSections)
{
[sections addObject:[self.collation sortedArrayFromArray:section collationStringSelector:selector]];
}
return sections;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] count];
}
I did a similar solution for a contacts application. I start by defining the alphabet myself, then you can add to each section the items that fit (case insensitive compare for the letters, Characterset for the numbers). After the list is filled, you can remove (or hide) the sections which have no entry.
I did a similar thing for one of my apps. To split titles I used this code:
NSString* alphabet = #"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
NSArray* indexes = [alphabet componentsSeparatedByString:#""];
for (NSString* title in self.titles) {
char c = [[title uppercaseString] characterAtIndex:0];
NSString* index = [NSString stringWithFormat:#"%c",c];
if ([alphabet rangeOfString:index].location == NSNotFound) {
//The title does not start with a valid letter
index = #"#";
}
NSMutableArray* sectionObjects = [self.sections objectForKey:index];
if (!sectionObjects) {
sectionObjects = [[NSMutableArray alloc] init];
[self.sections setObject:sectionObjects forKey:index];
}
[sectionObjects addObject:title];
}
Obviously this code is still missing the sort part since it's trivial.
I ended up getting this my index to work by this
-(NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector
{
self.collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionCount = [[self.collation sectionTitles] count];//section count is take from sectionTitles and not sectionIndexTitles
NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];
//create an array to hold the data for each section
for(int i = 0; i < sectionCount; i++)
{
[unsortedSections addObject:[NSMutableArray array]];
}
if ([self.catString isEqualToString:ARTISTS])
{
//put each object into a section
for (id object in array)
{
if (!object)
{
continue;
}
NSInteger index = [self.collation sectionForObject:object collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
}
else
{
NSInteger index;
for (id object in array)
{
Song *songItem = object;
NSString* charIndex;
if([songItem.songName length]<=2)
{
charIndex = [songItem.songName substringToIndex:1];
}
else if([songItem.songName length]<=3)
{
charIndex = [songItem.songName substringToIndex:2];
}
else if([songItem.songName length]<=4)
{
charIndex = [songItem.songName substringToIndex:3];
}
else if([songItem.songName length]>=5)
{
charIndex = [songItem.songName substringToIndex:4];
}
NSRegularExpression *regex = [[NSRegularExpression alloc]
initWithPattern:#"[a-zA-Z]" options:0 error:NULL];
NSUInteger matches = [regex numberOfMatchesInString:charIndex options:0
range:NSMakeRange(0, [charIndex length])];
if (matches >=2)
{
NSLog(#"matches %i",matches);
index = [self.collation sectionForObject:object collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
else
{
index = 26;
[[unsortedSections objectAtIndex:index] addObject:object];
}
NSLog(#"songItem %# is in index %i",songItem.songName, index);
}
}
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
//sort each section
for (NSMutableArray *section in unsortedSections)
{
[sections addObject:[self.collation sortedArrayFromArray:section collationStringSelector:selector]];
}
return sections;
}

Escaping punctuation, and 'The' prefix in song titles when collating a MPMediaQuery

The code works and populates the table with sections, but it has a flaw: It doesn't escape punctuation and 'The' prefixes in song titles, just like how the native music app does.
Would really appreciate some guidance on how I should go about doing this.
- (void)viewDidLoad
{
[super viewDidLoad];
MPMediaQuery *songQuery = [MPMediaQuery songsQuery];
self.songsArray = [songQuery items];
self.sectionedSongsArray = [self partitionObjects:self.songsArray collationStringSelector:#selector(title)];
}
- (NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector
{
UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionCount = [[collation sectionTitles] count];
NSMutableArray *unsortedSections = [NSMutableArray arrayWithCapacity:sectionCount];
for(int i = 0; i < sectionCount; i++)
{
[unsortedSections addObject:[NSMutableArray array]];
}
for (id object in array)
{
NSInteger index = [collation sectionForObject:object collationStringSelector:selector];
[[unsortedSections objectAtIndex:index] addObject:object];
}
NSMutableArray *sections = [NSMutableArray arrayWithCapacity:sectionCount];
for (NSMutableArray *section in unsortedSections)
{
[sections addObject:[collation sortedArrayFromArray:section collationStringSelector:selector]];
}
return sections;
}
I completely overlooked this. The answer here is to simply use MPMediaQuerySection. The Apple docs are there for a reason!
Cocotutch -
Here's the implementation that I used to index a query containing all the songs in my music library:
MPMediaQuery *allSongsQuery = [MPMediaQuery songsQuery];
// Fill in the all songs array with all the songs in the user's media library
allSongsArray = [allSongsQuery items];
allSongsArraySections = [allSongsQuery itemSections];
allSongsArraySections is an NSArray of MPMediaQuerySection, each of which has a title and a range. The NSArray object for section zero (with title #"A" in my case) has range.location of 0 and range.length of 158.
I return the range.length value for each section when numberOfRowsInSection is called for my UITableView. I use the range.location value in cellForRowAtIndexPath as the starting row of the section, and then add indexPath.row to it in order to arrive at the cell I need to return from my allSongsArray.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
....
// Return the number of rows in the section.
MPMediaQuerySection *allSongsArraySection = globalMusicPlayerPtr.allSongsArraySections[section];
return allSongsArraySection.range.length;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
...
MPMediaQuerySection *allSongsArraySection = globalMusicPlayerPtr.allSongsArraySections[indexPath.section];
rowItem = [globalMusicPlayerPtr.allSongsArray objectAtIndex:allSongsArraySection.range.location + indexPath.row];
....
}
Before using this I had tried to match the native music player's implementation by writing my own, and it didn't behave quite identically. Not only that, but the native indexing is MUCH faster!

Resources