Best way to grab an array of dictionaries from a plist and create sections based on the values for a key

3.5k views Asked by At

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!

2

There are 2 answers

0
Jonah On BEST ANSWER

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];
3
Matt Long On

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.