Implementing UITableView Sections from an NSArray of NSDictionary Objects

  • Twitter
  • Facebook
  • Digg
  • Reddit
  • StumbleUpon
  • del.icio.us
  • Google Bookmarks
December 10th, 2010 Posted by: (ELC) - posted under:Featured » Tutorials

If you’re working with a remote Web Service, your apps are probably displaying TableViews of objects. As soon as your dataset grows beyond 20 or 30 objects, it’s time to implement sections in your Table View. I’m going to show you how you can do this without too much trouble. In this example, we’ll use an array of dictionary objects (Books) to construct a single ‘sections’ dictionary that will be the basis for our TableView datasource.

Before we get started, you might want to clone the git repo containing the demo Xcode project for this post:

git clone git@github.com:elc/ICB_SectionedTableViewDemo.git

First let’s take a look at the UITableViewDataSource protocol methods that we’ll be using to get sections going:

    - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
    - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
    - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
    - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {

These are the four methods needed to show section headers, as well as the section index (which is the scrubber on the right side). Normally, with a established dataset (say one that already contains at least one item that starts with each letter of the alphabet), you can take some short cuts and return 26 for numberOfSectionsInTableView, hard code an array of A-Z to use for titleForHeaderInSection, etc. I’ve done that a few times for static datasets, but it’s really the wrong solution if you’re pulling data from a user-generated or otherwise dynamic datastore.

Instead what we’ll do is build a new dictionary entirely. The keys of this dictionary will correspond to our tableview section titles, and the values for those keys will be arrays containing our dictionary objects; these represent cells in the tableview.

Let’s start by establishing our sections:

 
    BOOL found;
 
    // Loop through the books and create our keys
    for (NSDictionary *book in self.books)
    {
        NSString *c = [[book objectForKey:@"title"] substringToIndex:1];
 
        found = NO;
 
        for (NSString *str in [self.sections allKeys])
        {
            if ([str isEqualToString:c])
            {
                found = YES;
            }
        }
 
        if (!found)
        {
            [self.sections setValue:[[NSMutableArray alloc] init] forKey:c];
        }
    }

If you’re implementing this code as you go, make sure you’ve setup ‘sections’ as a NSMutableDictionary property of your datasource (which is usually just your UITableViewController). So after this block of code runs, you’ll have a dictionary with a key for each unique first character of book->title. So if your books are:

On Intelligence
On The Road
Ishmael
Dune

Your self.sections will be:

‘O’ => empty NSMutableArray
‘I’ => empty NSMutableArray
‘D’ => empty NSMutableArray

Notice that the keys in your NSDictionary aren’t sorted alphabetically. They would be if your initial datasource (self.books) was sorted alphabetically; but there’s no guarantee on that. I’ll show you how to deal with this in a minute.

So the next step is to populate the empty arrays in self.sections with the appropriate NSDictionary objects from self.books:

    // Loop again and sort the books into their respective keys
    for (NSDictionary *book in self.books)
    {
        [[self.sections objectForKey:[[book objectForKey:@"title"] substringToIndex:1]] addObject:book];
    }

Now the books are compartmentalized into their respective sections. Next we need to sort the books within each section:

    // Sort each section array
    for (NSString *key in [self.sections allKeys])
    {
        [[self.sections objectForKey:key] sortUsingDescriptors:[NSArray arrayWithObject:[NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES]]];
    }

Again, we’re basing our sort off @”title”. I’ll leave @”title” in here for readability, but it would be wise to pull this out into it’s own NSString *sortByProperty so you can easily change it should you decide you want to base your sections/sorting on Author, Publication Year, etc.

At this point you’ll probably want to drop this in too:

    [self.tableView reloadData];

So now that we’ve constructed our self.sections, we can finally implement the UITableViewDataSource protocol methods I mentioned above:

#pragma mark -
#pragma mark Table view data source
 
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return [[self.sections allKeys] count];
}
 
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    return [[[self.sections allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section];
}
 
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section]] count];
}
 
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [[self.sections allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
}

This is where things get a little confusing. Since these methods are called with indexPath.section, which an NSInteger, and not the actual section key, we’re forced to get a little tricky. In order to make sure that our section indexes and section titles match up correctly AND are ordered alphabetically, we have to sort allKeys each time and then reference the resulting NSArray. It’s kind of ugly, but I’ve done all the annoying bracket matching for you already.

OK. So now that we have sections out of the way, let’s show some cells…

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier] autorelease];
    }
 
    NSDictionary *book = [[self.sections valueForKey:[[[self.sections allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:indexPath.section]] objectAtIndex:indexPath.row];
 
    cell.textLabel.text = [book objectForKey:@"title"];
    cell.detailTextLabel.text = [book objectForKey:@"description"];
 
    return cell;
}

That’s it! You should have a great looking sectioned tableView. If you have any questions or comments, you can get a hold me@matt_tuzzolo on Twitter.

  • http://icode.dreamvision-soft.com/blog/?p=94 Implementing UITableView Sections from an NSArray of NSDictionary Objects | iCode

    [...] Original post on iCodeBlog [...]

  • khushsingh

    Hi mate how would you do this with SQL as your data source?

  • http://blog.marcoscrispino.com Marcos Crispino

    Nice post, but I believe you can improve your code a little bit.

    To fill the sections dictionary, you do the following:
    1. make a first pass on self.books to collect the dictionary keys
    2. make a second pass to store the books in the arrays
    3. sort each one of the arrays

    You could do it like this:
    1. sort the array first (I mean self.books)
    2. make only one pass, and
    2.1 if the first letter of the book is different from the last one, create the dictionary key
    2.2 add the book to the array

    Plus, you could keep another array to hold the mapping between indexPath.section and the name of the section.

  • http://blog.marcoscrispino.com Marcos Crispino

    Oh, another thing…

    If you make the change I propose, then you don’t need to check if the key already exists in the dictionary, but if you ever need to, you can do it much simpler:

    BOOL found = [[self.sections allKeys] containsObject:c];
    :)

  • http://difusi.com Michael Wright

    Brilliant Marcos.!.

  • Romulo

    Why is that im causing a memory leak on the [self.sections setValue:[[NSMutableArray alloc] init] forKey:c]; part? Looks like it never release the alloc.

  • Karthikeyan

    Great ,Thanks ,you save my work,cool, keep up the work,

  • Naveen

    Thanks for the Nice, what if i want to sort in descending Order?

  • http://www.nudibranch.com.au Gary Cobb

    Yes how would you do this with SQL as your data source?
    This would be very useful. Please help.

  • bob

    for (NSString *key in [self.sections allKeys])
    should be written as
    for (NSString *key in self.sections)

    @Marcos Crispino:
    It could be written even simpler:
    BOOL found = [self.sections objectForKey:c] != nil;

  • http://www.nudibranch.com.au Gary Cobb

    How can I get the source code for this project? I am trying to index my table from am SQLite database. Would someone like to see my code and help me from there??
    Thank you all.

  • http://www.nudibranch.com.au Gary Cobb

    Thanks Bob for your reply by the way! I have different code than the one in this tutorial as I have asked above and need some help getting an Index.

  • http://www.nudibranch.com.au Gary Cobb

    Actually I have the Index. It is the sections and the linking of the index to the sections that is giving me problems.

  • Jiaminghome

    Your article can stimulate the ear, and excite eyes.

  • Ascott6606

    I like your tutorial. I wanted to add a detail view. how do I push to a detail controller?

  • pprevalkar

    I like the tutorial. I have implemented it in my code but when i select particular cell it does not display proper details. can u help me in implementing didSelectRowAtIndexPath method

  • Carol

    What in the world is GIT and REPO and CLONE????

    Before we get started, you might want to clone the git repo containing the demo Xcode project for this post:

    git clone git@github.com:elc/ICB_SectionedTableViewDemo.git

  • Debby

    Are there any advantanges/disadvanages when I get my data from sqlite and store it in a data source as:

    A dictionary, containing keys + arrays. (Referenced by key-word)
    An array, containing arrays. (Referenced by index-number)

  • Satyam90

    This is really good tutorial to learn about table view’s sections.
    Thanks a lot for the tutorial.

  • Russell

     Matt, this tutorial has helped me more than words can express.  You have made clear what so many books have failed to explain!

    I’m struggling with deleting a row however.  All the examples I have found in books, stackoverflow.com, etc refer to arrays not dictionaries.  I’m populating my tables using your method reading from a plist.  I get that I have to write the plist after the row is deleted, I just can’t seem to figure out how to handle the deletion.

    Any assistance much appreciated.  Rock on!

  • http://twitter.com/stur Stuart Rutter

    Thanks, this helped me out a lot.

  • Groumfy

    Shouldn’t:

    [[self.sections objectForKey:[[book objectForKey:@"title"] substringToIndex:1]] addObject:book];

    be replaced by:
    [[self.sections setObjet:book forKey:[[book objectForKey:@"title"] substringToIndex:1]]];?

  • Mark

    THanks dude, this helped me a lot. Thought the apple API would provide more but I see it is quite manual to “group” data sets.

  • Pksp_1988

    i want this project source code

  • Dojls

    Very good and optimized example. Thank you also for the explaining comments, wouldn’t understand a thing without these comments. THANK’S  ONE MORE TIME.

  • JMeds

    Thank you! Your steps were easy to follow and I was able to adapt your examples to fit my program very easily.  

  • bert hoorne

    SupaDupah, post helped me out, should be cool if there would be a small section in this tutorial that these guys –>  return [[[self.sections allKeys] sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)] objectAtIndex:section]; explain.

    Supa post though :)

  • Th4lf

    I hate incomplete implementation tutorials

  • Jk
  • Lawl

    3=================D

  • Sawancool

     Thanks

blog comments powered by Disqus
canakkale canakkale canakkale balik tutma search canakkale vergi mevzuati