views:

694

answers:

3

I have a plist that contains an array with a collection of dictionaries. The dictionaries in the array are used to create a fairly complex tableview with 2 images and 2 lines of text in each cell. I am wanting to create sections in the tableview based on the first letter for the value corresponding to the "MainText" key.

Here is the plist format. In this example there should be 2 sections, one for the dictionary with the "MainText" string starting with "A" and one for the 2 dictionaries with the "MainText" strings starting with "B".

<dict>
<key>Rows</key>
<array>
    <dict>
        <key>MainText</key>
        <string>A sometext</string>
        <key>SecondaryText</key>
        <string>somemoretext</string>
        <key>PrimaryIcon</key>
        <string>firsticon.png</string>
        <key>SecondaryIcon</key>
        <string>secondicon.png</string>
    </dict>
    <dict>
        <key>MainText</key>
        <string>B sometext</string>
        <key>SecondaryText</key>
        <string>somemoretext</string>
        <key>PrimaryIcon</key>
        <string>firsticon.png</string>
        <key>SecondaryIcon</key>
        <string>secondicon.png</string>
    </dict>
            <dict>
        <key>MainText</key>
        <string>B moreTextStartingWith"B"</string>
        <key>SecondaryText</key>
        <string>somemoretext</string>
        <key>PrimaryIcon</key>
        <string>firsticon.png</string>
        <key>SecondaryIcon</key>
        <string>secondicon.png</string>
    </dict>
</array>

I've written the code for extracting the first letters from the string at the "MainText" key, sorting them and creating the section headers. I also have the code for setting the correct number of rows in each section. Here's some of the code I've been tangling with in my viewDidLoad method.

//Create an array sorted by the strings in "MainText" of the AppDelegate.data
    NSSortDescriptor *sortDescriptor;
    sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"MainText" ascending:YES] autorelease];
    NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
    NSArray *sortedArray;
    sortedArray = [[AppDelegate.data objectForKey:@"Rows"] sortedArrayUsingDescriptors:sortDescriptors];


//Now set tableDataSource equal to sortedArray
    self.tableDataSource = sortedArray;

//Create a set of the first letters used by the strings in MainText
    NSMutableSet *firstCharacters = [NSMutableSet setWithCapacity:0];
    for( NSString *string in [tableDataSource valueForKey:@"MainText"] )
        [firstCharacters addObject:[string substringToIndex:1]];

//Create a sorted array of first letters used by strings in MainText
    self.arrayOfInitialLetters = [[firstCharacters allObjects] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];

//Create an array of the MainText of each item in tableDataSource   
    NSMutableArray * mainTextArray = [tableDataSource valueForKey:@"MainText"];

How should I go about getting the correct rows in my cellForRowAtIndexPath method? Currently each section just displays the same cells starting at the first. I need to somehow define in my data source that there are different sections. Thanks for the help this has been a real struggle!

A: 

Your XML looks wrong. It seems that it should be like this:

<dict>
<key>Rows</key>
<array>
        <dict>
                <key>MainText</key>
                <string>A sometext</string>
                <key>SecondaryText</key>
                <string>somemoretext</string>
                <key>PrimaryIcon</key>
                <string>firsticon.png</string>
                <key>SecondaryIcon</key>
                <string>secondicon.png</string>
        </dict>
        <dict>
                <key>MainText</key>
                <string>B sometext</string>
                <key>SecondaryText</key>
                <string>somemoretext</string>
                <key>PrimaryIcon</key>
                <string>firsticon.png</string>
                <key>SecondaryIcon</key>
                <string>secondicon.png</string>
        </dict>
</array>
</dict>

You can create a category for NSDictionary that will allow you to sort your data. Something like this:

@interface NSDictionary (Sorting) 

-(NSComparisonResult)compareRows:(NSDictionary*)row;

@end

@implementation NSDictionary (Sorting)

-(NSComparisonResult)compareRows:(NSDictionary*)row;
{
    NSString *rowMainText = [row objectForKey:@"MainText"];
    NSString *selfMainText = [self objectForKey:@"MainText"];

    return [selfMaintext localizedCompare:rowMainText];
}
@end

Then you can perform your sort like this:

[rows sortUsingSelector:@selector(compareRow:)];

The variable rows would need to be mutable since the sort will be performed internally.

Matt Long
Thanks. I was correcting the plist as you posted this answer :)... I somehow messed it up when copying it into this post.Before I try to implement this, does it group the separate dictionaries into sections based on the value for "MainText" or does it just sort them?Thanks for the help, this one has been making me crazy. I've been experimenting with UILocalizedIndexedCollation as well to see if it may do the trick.
Jonah
Okay, just to clarify. Are you suggesting to create a new .h and .m file pair to store these NSComparisonResult methods? Or should I add them to my RootViewController file? I guess I'm not sure exactly what you mean by "create a category for NSDictionary".Thanks so much for your help!
Jonah
This will just sort by "MainText" so now I can see this may not help you. The NSComparisonResult methods can be placed in their own file if you like, something like NSDictionry+Sorting.h/m, or you can just declare them in your view controller class--which is generally what I do.
Matt Long
A: 

I finally got this working. Here is the code for my viewDidLoad method. It may not be the cleanest method to obtain my result but it works great! Still, I welcome any suggestions.

- (void)viewDidLoad {
[super viewDidLoad];

// Load the UICustomTabViewController
    UICustomTabViewController *tvController = [[UICustomTabViewController alloc]
         initWithNibName:@"TabViewController" bundle:nil];
 self.tabViewController = tvController;
 [tvController release];

 YourAppDelegate *AppDelegate = (YourAppDelegate *)[[UIApplication
         sharedApplication] delegate];


// Create an array sorted by CommonName of the AppDelegate.data
 NSSortDescriptor *sortDescriptor = [[[NSSortDescriptor alloc] initWithKey:@"CommonName" ascending:YES] autorelease];
 NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
 NSArray *sortedArray;
 sortedArray = [[AppDelegate.data objectForKey:@"Rows"] sortedArrayUsingDescriptors:sortDescriptors];

// Now set tableDataSource equal to sortedArray
    self.tableDataSource = sortedArray;

// Create a set of the first letters used by the strings in CommonName and sort them
 NSMutableSet *firstCharacters = [NSMutableSet setWithCapacity:0];
 for( NSString *string in [tableDataSource valueForKey:@"CommonName"] )
  [firstCharacters addObject:[string substringToIndex:1]];
  self.arrayOfInitialLetters = [[firstCharacters allObjects]
        sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];

    self.sectionedDictionaryByFirstLetter = [NSMutableDictionary dictionary];           

// All data sorted in sections by initial letter of "CommonName"
 NSDictionary *eachItemList;  //PUTS ALL THE DATA FOR EACH ITEM IN IT'S OWN SECTION
 for (eachItemList in tableDataSource) //eachElementList is an array with a section for each item
 {
  NSDictionary *aDictionary = [[NSDictionary alloc] initWithDictionary:eachItemList];
  NSString *firstLetterString;
  firstLetterString = [[aDictionary valueForKey:@"CommonName"]substringToIndex:1];

  NSMutableArray *existingArray;

  if (existingArray = [sectionedDictionaryByFirstLetter valueForKey:firstLetterString]) 
  {
   [existingArray addObject:eachItemList];
  } else {
   NSMutableArray *tempArray = [NSMutableArray array];
   [sectionedDictionaryByFirstLetter setObject:tempArray forKey:firstLetterString];
   [tempArray addObject:eachItemList];
  }
  [aDictionary release];
  [eachItemList release];
  NSLog(@"nameIndexesDictionary:%@", sectionedDictionaryByFirstLetter);
 }

Then this is how I was able to get the correct row in my cellForRowAtIndexPath method

    NSInteger section = [indexPath section];
    NSString *key = [arrayOfInitialLetters objectAtIndex:section]; 
    NSArray *nameSection = [sectionedDictionaryByFirstLetter objectForKey:key];
    NSDictionary *dictionary = [nameSection objectAtIndex:indexPath.row];
Jonah
+1  A: 

Jonah, Is this for working with an array of dictionaries from a plist or a dictionary of arrays? I'm trying to find an example of an array of dictionary's so I can understand how this process works. I think this is what I'm looking at, is it?

James B.
It's working great for me. See the code in my answer below. I'm giving you an up vote to get your rep going.
Jonah
Thanks Jonah, much appreciated. Thanks for the posting as well, it's really useful when you're learning to see how other people go about doing things. This problem drove me nuts for ages.
James B.