Autogroup UITableView alphabetically

12,608

Solution 1

First of All Define sections

self.sections = [NSArray arrayWithObjects:@"#", @"a", @"b", @"c", @"d", @"e", @"f", @"g", @"h", @"i", @"j", @"k", @"l", @"m", @"n", @"o", @"p", @"q", @"r", @"s", @"t", @"u", @"v", @"w", @"x", @"y", @"z", nil];

then

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
   return 27;
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
   return self.sections;
}    

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString*)title atIndex:(NSInteger)index
{
   return index;
}

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
   return [self.sections objectAtIndex:section];
}

After that filter by alphabets

NSArray *sectionArray = [vendorList filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"SELF beginswith[c] %@", [self.sections objectAtIndex:section]]]; 
            rowCount = [sectionArray count];

Solution 2

I have a pod just for this:

https://github.com/chrisladd/CGLAlphabetizer/

pod 'CGLAlphabetizer', '~> 0.1'

It creates a dictionary of arrays keyed by letters of the alphabet from a single array of objects and an arbitrary keyPath.

alphabetizedDictionary = [CGLAlphabetizer alphabetizedDictionaryFromObjects:anArray usingKeyPath:@"keyPath"];

So, assuming you had the model object that looked like this:

@interface CGLContact : NSObject
@property (nonatomic) NSString *firstName;
@property (nonatomic) NSString *lastName;

@property (nonatomic, readonly) NSString *fullName;

@end

Your tableViewController implementation might look something like this:

static NSString * const CGLContactsCellIdentifier = @"CGLContactsCellIdentifier";

@interface CGLContactsTableViewController ()
@property (nonatomic) NSDictionary *alphabetizedDictionary;
@property (nonatomic) NSArray *sectionIndexTitles;
@end

@implementation CGLContactsTableViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    [self.tableView registerClass:[UITableViewCell class] forCellReuseIdentifier:CGLContactsCellIdentifier];
}

- (void)setContacts:(NSArray *)contacts {
    _contacts = contacts;
    self.alphabetizedDictionary = [CGLAlphabetizer alphabetizedDictionaryFromObjects:_contacts usingKeyPath:@"lastName"];
    self.sectionIndexTitles = [CGLAlphabetizer indexTitlesFromAlphabetizedDictionary:self.alphabetizedDictionary];

    [self.tableView reloadData];
}

- (CGLContact *)objectAtIndexPath:(NSIndexPath *)indexPath {
    NSString *sectionIndexTitle = self.sectionIndexTitles[indexPath.section];
    return self.alphabetizedDictionary[sectionIndexTitle][indexPath.row];
}

#pragma mark - Table view data source

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return self.sectionIndexTitles;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [self.sectionIndexTitles count];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    NSString *sectionIndexTitle = self.sectionIndexTitles[section];
    return [self.alphabetizedDictionary[sectionIndexTitle] count];
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CGLContactsCellIdentifier forIndexPath:indexPath];

    CGLContact *contact = [self objectAtIndexPath:indexPath];
    cell.textLabel.text = contact.fullName;

    return cell;
}

@end

Populated with every human who has gone to space:

demo

Solution 3

i did the following:

AutoSectionTableViewDataSource.h

@protocol AutoSectionTableViewDataSource
- (NSString*)tableView:(UITableView*)tableView sectionNameAtIndexPath:(NSIndexPath*)indexPath;
@end
@interface AutoSectionTableViewDataSource : NSObject <UITableViewDataSource>
- (id)initWithDataSource:(NSObject<UITableViewDataSource,AutoSectionTableViewDataSource>*)dataSource;
@end

AutoSectionTableViewDataSource.m

@interface AutoSectionTableViewDataSource ()
@property(weak) NSObject<UITableViewDataSource,AutoSectionTableViewDataSource> *dataSource;
@end
@implementation AutoSectionTableViewDataSource

- (id)initWithDataSource:(NSObject<UITableViewDataSource,AutoSectionTableViewDataSource>*)dataSource
{
    self = [super init];
    self.dataSource = dataSource;
    return self;
}

- (BOOL)respondsToSelector:(SEL)selector
{
    if ([super respondsToSelector:selector]) return YES;
    if (self.dataSource && [self.dataSource respondsToSelector:selector]) return YES;
    return NO;
}

- (void)forwardInvocation:(NSInvocation*)invocation
{
    if (self.dataSource && [self.dataSource respondsToSelector:invocation.selector]) {
        [invocation invokeWithTarget:self.dataSource];
    } else {
        [self doesNotRecognizeSelector:invocation.selector];
    }
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)selector
{
    if (self.dataSource) return [self.dataSource methodSignatureForSelector:selector];
    return nil;
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    NSInteger rows = [self.dataSource tableView:tableView numberOfRowsInSection:0];
    NSMutableSet *set = [[NSMutableSet alloc] init];
    for (NSInteger row=0; row<rows; row++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
        NSString* sectionName = [self.dataSource tableView:tableView sectionNameAtIndexPath:indexPath];
        [set addObject:sectionName];
    }
    return set.count;
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    NSInteger rows = [self.dataSource tableView:tableView numberOfRowsInSection:0];
    NSMutableDictionary *tmp = [[NSMutableDictionary alloc] init];
    NSInteger count = 0;
    for (NSInteger row=0; row<rows; row++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
        NSString* sectionName = [self.dataSource tableView:tableView sectionNameAtIndexPath:indexPath];
        if (!tmp[sectionName]) {
            tmp[sectionName] = @(tmp.count);
        }
        if ([tmp[sectionName] intValue] == section) {
            count++;
        }
    }
    return count;
}

- (NSString*)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    NSInteger rows = [self.dataSource tableView:tableView numberOfRowsInSection:0];
    NSMutableSet *set = [[NSMutableSet alloc] init];
    for (NSInteger row=0; row<rows; row++) {
        NSIndexPath *indexPath = [NSIndexPath indexPathForRow:row inSection:0];
        NSString* sectionName = [self.dataSource tableView:tableView sectionNameAtIndexPath:indexPath];
        if (![set containsObject:sectionName]) {
            [set addObject:sectionName];
            if (set.count - 1 == section) {
                //NSLog(@"AutoSectionTableViewDataSource titleForHeaderInSection:%d -> %@", section, sectionName);
                return sectionName;
            }
        }
    }
    return nil;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    NSInteger rows = [self.dataSource tableView:tableView numberOfRowsInSection:0];
    NSMutableDictionary *tmp = [[NSMutableDictionary alloc] init];
    NSInteger count = 0;
    for (NSInteger row=0; row<rows; row++) {
        NSIndexPath *rowIndexPath = [NSIndexPath indexPathForRow:row inSection:0];
        NSString* sectionName = [self.dataSource tableView:tableView sectionNameAtIndexPath:rowIndexPath];
        if (!tmp[sectionName]) {
            tmp[sectionName] = @(tmp.count);
        }
        if ([tmp[sectionName] intValue] == indexPath.section) {
            count++;
            if (count-1 == indexPath.row) {
                UITableViewCell *cell = [self.dataSource tableView:tableView cellForRowAtIndexPath:rowIndexPath];
                return cell;
            }
        }
    }
    return nil;
}

@end

and then in your code:

- (NSString*)tableView:(UITableView *)tableView sectionNameAtIndexPath:(NSIndexPath *)indexPath
{
    return @"this would be your section name for indexPath.row"
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return total_number_of_rows;
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    return cell_for_indexPath.row;
}

- (void)viewDidLoad
{
    [super viewDidLoad];
    // instead of self.tableView.dataSource = self; use:
    self.autoSectionDataSource = [[AutoSectionTableViewDataSource alloc] initWithDataSource:self];
    self.tableView.dataSource = self.autoSectionDataSource;
}
Share:
12,608
flohei
Author by

flohei

I'm an iOS Software Engineer from Bavaria, Germany.

Updated on June 04, 2022

Comments

  • flohei
    flohei almost 2 years

    Is there a way to auto group/auto-section my UITableView alphabetically? I have a huge array which is being displayed nicely, but it would be even better if I had the sections like in Contacts.app.

    Best
    –f