Here is a simple solution using the third-party TLIndexPathTools data model class TLIndexPathDataModel
. It is specifically designed to work with indexes and sections, so you can accomplish what you need with minimal complexity. And here is a complete working demonstration .
First define a class to represent the contact. This gives you the ability to define firstName
, lastName
, displayName
and sectionName
:
@interface Contact : NSObject @property (strong, nonatomic, readonly) NSString *firstName; @property (strong, nonatomic, readonly) NSString *lastName; @property (strong, nonatomic, readonly) NSString *displayName; @property (strong, nonatomic, readonly) NSString *sectionName; - (instancetype)initWithFirstName:(NSString *)firstName lastName:(NSString *)lastName; @end
The sectionName
property simply returns the first character firstName
. Then, if subclasses of the TLTableViewController
table, the implementation will look something like this:
@implementation ContactsTableViewController - (void)viewDidLoad { [super viewDidLoad]; NSMutableArray *contacts = [NSMutableArray array]; //get actual list of contacts here... [contacts addObject:[[Contact alloc] initWithFirstName:@"John" lastName:@"Doe"]]; [contacts addObject:[[Contact alloc] initWithFirstName:@"Sally" lastName:@"Smith"]]; [contacts addObject:[[Contact alloc] initWithFirstName:@"Bob" lastName:@"Marley"]]; [contacts addObject:[[Contact alloc] initWithFirstName:@"Tim" lastName:@"Cook"]]; [contacts addObject:[[Contact alloc] initWithFirstName:@"Jony" lastName:@"Ives"]]; [contacts addObject:[[Contact alloc] initWithFirstName:@"Henry" lastName:@"Ford"]]; //sort by section name [contacts sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"sectionName" ascending:YES]]]; //set the data model self.indexPathController.dataModel = [[TLIndexPathDataModel alloc] initWithItems:contacts sectionNameKeyPath:@"sectionName" identifierKeyPath:nil]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell = [super tableView:tableView cellForRowAtIndexPath:indexPath]; //get contact for index path from data model and configure cell Contact *contact = [self.indexPathController.dataModel itemAtIndexPath:indexPath]; cell.textLabel.text = contact.displayName; return cell; } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { return self.indexPathController.dataModel.sectionNames; } - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { return index; } @end
The main thing is that TLIndexPathDataModel
automatically organizes your data into sections using the sectionNameKeyPath
set to @ "sectionName". Then, in your opinion, the logic of the controller, you can easily access the contact for a given index path by calling:
Contact *contact = [self.indexPathController.dataModel itemAtIndexPath:indexPath];
Update
In fact, you want to do second-level sorting on the display name:
[contacts sortUsingDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"sectionName" ascending:YES], [NSSortDescriptor sortDescriptorWithKey:@"displayName" ascending:YES]]];
update # 2
There is a new block-based initializer for TLIndexPathDataModel
that makes this a lot easier if you don't want to define a custom data object just to add the sectionNameKeyPath
property. For example, you can use the new initializer to organize the list of strings, as shown in the "Blocks" sample project :
- (void)viewDidLoad { [super viewDidLoad]; NSArray *items = [@[ @"Fredricksburg", @"Jelly Bean", ... @"Metadata", @"Fundamental", @"Cellar Door"] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];