I'm trying to replicate the contacts table view in my app. So I have a list of contacts displayed in a table view however I would like the table view to be sectioned into all the letters of the alphabet and the names of the contacts to be placed in the section related to the lister letter of their first name. Like this
How do I get that view? So far this is all I have done.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return [displayNames count];
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
static NSString *simpleTableIdentifier = @"ContactsCell";
/*ContactCell *cell = (ContactCell *)[tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
NSArray *nib = [[NSBundle mainBundle] loadNibNamed:@"ContactCell" owner:self options:nil];
cell = [nib objectAtIndex:0];
}*/
UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
if (cell == nil) {
cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:simpleTableIdentifier];
}
// cell.name.text = [displayNames
// objectAtIndex:indexPath.row];
UILabel *namer = (UILabel *)[cell viewWithTag:101];
namer.text=[displayNames
objectAtIndex:indexPath.row];
/*
Get the picture urls from the picture array and then
I loop through the array and initialize all the urls with
a NSUrl and place the loaded urls in anouther nsmutable array
*/
urls = [[NSMutableArray alloc] init];
for (id object in pictures) {
//NSDictionary *names = res[@"image"];
NSString *name = object;
NSURL *url=[[NSURL alloc] initWithString:name];
[urls addObject:url];
}
// cell.profile.image= [UIImage imageWithData:[NSData dataWithContentsOfURL: [urls objectAtIndex:indexPath.row]]];
UIImageView *profiler = (UIImageView *)[cell viewWithTag:100];
profiler.image= [UIImage imageWithData:[NSData dataWithContentsOfURL: [urls objectAtIndex:indexPath.row]]];
return cell;
}
Here is an easy solution using the 3rd party TLIndexPathTools data model class TLIndexPathDataModel
. It is specifically designed for working with index paths and sections, so you can accomplish what you need with minimal complexity. And here is a full, working demo.
First define a class to represent a contact. This gives you a place 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 just returns the first character of the firstName
. Then if your table view subclasses TLTableViewController
, the implementation would 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 key thing is that TLIndexPathDataModel
automatically organizes your data into sections using the sectionNameKeyPath
set to @"sectionName". Then in your view controller logic, you can easily access the contact for a given index path by calling:
Contact *contact = [self.indexPathController.dataModel itemAtIndexPath:indexPath];
update
You'd actually want to do a second-level sort on 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 a sectionNameKeyPath
property. For example, one can use the new initializer to organize a list of strings as illustrated in the "Blocks" sample project:
- (void)viewDidLoad
{
[super viewDidLoad];
NSArray *items = [@[
@"Fredricksburg",
@"Jelly Bean",
...
@"Metadata",
@"Fundamental",
@"Cellar Door"] sortedArrayUsingSelector:@selector(caseInsensitiveCompare:)];
//generate section names by taking the first letter of each item
self.indexPathController.dataModel = [[TLIndexPathDataModel alloc] initWithItems:items
sectionNameBlock:^NSString *(id item) {
return [((NSString *)item) substringToIndex:1];
} identifierBlock:nil];
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With