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.