One of the lesser used features of UITableView is the section index. A section index is a list that flows down the side of the table and allows users to quickly move around the data. Contacts uses an index:

To implement an index the table views data source must implement two methods:
sectionIndexTitlesForTableView:tableView:sectionForSectionIndexTitle:atIndex:sectionIndexTitlesForTableView: supplies an array of strings which are displayed as the index. In the case of the above screen shot, this would be an array of A to Z, with ‘search’ prepended and ‘#’ appended. (The table view substitutes the string {search} with the search icon.)
– tableView:sectionForSectionIndexTitle:atIndex: is used to tell the table view which section to jump to when an index is selected. This method exists to allow complicated mappings between a index and a section. For example, imagine that the index was configured like the screen shot above but the table only contains the following data:
| Section | Value |
|---|---|
| B | Brown, James |
| D | Dylan, Bob |
| Z | Zappa, Frank |
The index contains 28 items, but the table only contains 3 sections. tableView:sectionForSectionIndexTitle:atIndex: allows the data source to map the values to the index to a table section. The following table shows what tableView:sectionForSectionIndexTitle:atIndex: should return given the above data (let’s ignore search for the time being):
Section to jump to
| Selected index | Section to jump to |
|---|---|
| A | B |
| B | B |
| C | B |
| D | D |
| E-Y | D |
| Z | Z |
| # | Z |
That’s the basics of a table view index. It wouldn’t be too tricky to implement sectionIndexTitlesForTableView: and tableView:sectionForSectionIndexTitle:atIndex: for the above data. However, things get a lot more complicated when we take into account languages other than English. Here’s Contacts in English and Swedish:

There are a few things that these screen shots illustrate:
Just taking these differences into account would take a fair while, but imagine having to do that for all 34 languages supported by iOS. That would be painful. Thankfully these problems are solved by UILocalizedIndexedCollation. UILocalizedIndexedCollation performs 3 tasks:
Here’s how to implement it:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
}
//@property(readwrite, copy, nonatomic) NSArray *tableData;
-(void)viewDidLoad
{
NSArray *objects = aMagicalSourceOfWonderfullyInterestingObjects;
self.tableData = [self partitionObjects:objects collationStringSelector:@selector(title)];
}
-(NSArray *)partitionObjects:(NSArray *)array collationStringSelector:(SEL)selector
{
UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];
NSInteger sectionCount = [[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]];
}
//put each object into a section
for (id object in array)
{
NSInteger index = [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:[collation sortedArrayFromArray:section collationStringSelector:selector]];
}
return sections;
}
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
//we use sectionTitles and not sections
return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] count];
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [[self.tableData objectAtIndex:section] count];
}
- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
BOOL showSection = [[self.tableData objectAtIndex:section] count] != 0;
//only show the section title if there are rows in the section
return (showSection) ? [[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:section] : nil;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
id object = [[self.tableData objectAtIndex:indexPath.section] objectAtIndex:indexPath.row];
// configure the cell
//...
}
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
//sectionForSectionIndexTitleAtIndex: is a bit buggy, but is still useable
return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index];
}
That’s the basics of UILocalizedIndexedCollation. I haven’t covered everything. The areas left to cover are work arounds for a few bugs with UILocalizedIndexedCollation, how to add a search section and integrating with Core Data, but I haven’t figured out an elegant, reusable solution yet. When I do I’ll write it up and post it here.