Search Posts in my Blog

Wednesday 3 October 2012

Create Indexed UITableView



Tackling that handy index on the right side of table views is really not that complicated. In fact, it only requires that you implement two methods. In this tutorial, I’m going to take you step-by-step on how to wire it all together.

Indexed Table 2


1 .Set up your project

In Xcode, go to File -> New Project and choose Navigation-based Application from the iPhone OS tab. Name the applicationIndexedTable. At this point, you should have a runnable app with an empty table in it.

2. Create a data source

Instead of hardcoding a bunch of values, country names and whatnot, let’s create a simple method that will generate a bunch of strings from the letters of the alphabet so that our UITableView has something to work with.
Create a new class called DataGenerator. Right-click on the Classes group in the project browser and choose New File. Let the file be an Objective-C class (subclass of NSObject) and name it DataGenerator. This class will only have one static method (for now) that will simply return an array of words from letters. As such, we’ll name it wordsFromLetters.

In DataGenerator.h, insert the following code:
@interface DataGenerator : NSObject {
}
 
+ (NSArray *) wordsFromLetters;
 
@end
Now, let’s implement this method. Open DataGenerator.m and put this code in it:
@implementation DataGenerator
 
#define WORD_LENGTH 5
 
static NSString *letters = @"abcdefghijklmnopqrstuvwxyz";
 
+ (NSArray *) wordsFromLetters {
    NSMutableArray *content = [NSMutableArray new];
 
    for (int i = 0; i < [letters length]; i++ ) {
        NSMutableDictionary *row = [[[NSMutableDictionary alloc] init] autorelease];
        char currentWord[WORD_LENGTH + 1];
        NSMutableArray *words = [[[NSMutableArray alloc] init] autorelease];
 
        for (int j = 0; j < WORD_LENGTH; j++ ) {
            if (j == 0) {
                currentWord[j] = toupper([letters characterAtIndex:i]);
            }
            else {
                currentWord[j] = [letters characterAtIndex:i];
            }
            currentWord[j+1] = '\0';
            [words addObject:[NSString stringWithCString:currentWord encoding:NSASCIIStringEncoding]];
        }
        char currentLetter[2] = { toupper([letters characterAtIndex:i]), '\0'};
        [row setValue:[NSString stringWithCString:currentLetter encoding:NSASCIIStringEncoding]
               forKey:@"headerTitle"];
        [row setValue:words forKey:@"rowValues"];
        [content addObject:row];
    }
 
    return content;
}
 
@end
I’m not going to spend a whole of time explaining what’s going on here because this tutorial is really about adding an index bar to your UITableView. But let’s briefly talk what’s it all about. This method loops through an array letters one-by-one. For each letter it then generates a bunch of words to fill up our content. The final structure of the NSArray we’re returning is going to look something like this:
NSArray =>
  1. NSDictionary
    • headerTitle => ‘A’
    • rowValues => {”A”, “Aa”, “Aaa”, “Aaaa”}
  2. NSDictionary
    • headerTitle => ‘B’
    • rowValues => {”B”, “Bb”, “Bbb”, “Bbbb”}
  3. etc.
You’ll see how we’re using this array later on. Also note that this list is implicitly ordered.

3. Fill in UITableView with values from DataGenerator

Next we’re going to populate the currently empty table with data we get from our data generator. Open RootViewController.h and add these two instance variables to the class:
#import "DataGenerator.h"
 
@interface RootViewController : UITableViewController {
    NSArray *content;
    NSArray *indices;
}
content will hold our array of dictionaries while indices will hold all initial letters for each word in the list. Let's fill those up. OpenRootViewController.m implementation file and override the method viewDidLoad with the following:
- (void)viewDidLoad {
    [super viewDidLoad];
    content = [DataGenerator wordsFromLetters];
    indices = [[content valueForKey:@"headerTitle"] retain];
}
You can see that we're using [DataGenerator wordsFromLetters] to simply fill in the content variable. The second line of that method returns all keys from the dictionaries in content as an array. So indices now holds all letters of the alphabet.
We next override the two methods that tell our UITableView how many sections it has and how many rows there are in each section.
// Customize the number of sections in the table view.
- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [content count];
}
 
// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    return [[[content objectAtIndex:section] objectForKey:@"rowValues"] count] ;
}
The number of sections is equal to the number of letters in our list and the number of rows of each section is equal to the count of each array under its corresponding letter.
Finally, we implement cellForRowAtIndexPath so that it displays words from our list in the table. We handle headers for sections intitleForHeaderInSection. This method queries our content at a particular section and demands a headerTitle to be returned as header of that section.
// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
 
    static NSString *CellIdentifier = @"Cell";
 
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
    if (cell == nil) {
        cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                       reuseIdentifier:CellIdentifier] autorelease];
    }
    cell.textLabel.text = [[[content objectAtIndex:indexPath.section] objectForKey:@"rowValues"]
                           objectAtIndex:indexPath.row];
 
    return cell;
}
You should be able to run your app now (CMD + R) and see something like this:
Indexed Table 01

4. Add index to the table

There is nothing new in what we've done so far. We simply filled in a UITableView with some data. We're now ready to add our index to it. For that, we'll need to implement two methods: sectionIndexTitlesForTableView and sectionForSectionIndexTitle.
Add this code to RootViewController.m:
- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [content valueForKey:@"headerTitle"];
}
 
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [indices indexOfObject:title];
}
sectionIndexTitlesForTableView: In order to properly render the index, the UITableView needs to know all index titles to display. This method returns an array of strings containing all indices. In our case, A, B, C, D, etc.
sectionForSectionIndexTitle: When you start scrolling through the index, the table needs to know how far down/up to scroll so that the letter you're touching corresponds the appropriate section in the table. This method returns an index value (integer) of the section you're currently touching in the index.
One more thing we need to do before we run our app is to add titles to each section header in UITableView.
- (NSString *)tableView:(UITableView *)aTableView titleForHeaderInSection:(NSInteger)section {
 return [[content objectAtIndex:section] objectForKey:@"headerTitle"];
 
}

5. Run your app

If everything went well, you should now be able to run your app and see the final product. You'll see the index on the right and scrolling over it will scroll the table appropriately.

indexedtable02

Conclusion

VoilĂ ! That wasn't that hard was it? The main get-away from this tutorial are the two methods you need to implement in order for the index to show up and function properly: sectionIndexTitlesForTableView: and sectionForSectionIndexTitle:.

The complete source code for this tutorial can be found here: Indexed UITableView