iPhone UITableView. How do turn on the single letter alphabetical list like the Music App?

IosIphoneObjective CUitableview

Ios Problem Overview


In the iPhone music app, selecting Artist, Songs, or Albums presents a tableView with a verticl list of single letters at the righthand side of the UI that enables rapid scrolling. How do I enable this functionality in my app?

Cheers, Doug

Ios Solutions


Solution 1 - Ios

Supply your own index characters:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
	return[NSArray arrayWithObjects:@"a", @"e", @"i", @"m", @"p", nil];
}

and then:

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

You will need sections.

Solution 2 - Ios

Something else you have to consider is localizing the sections for each language. After digging around a bit, I found UILocalizedIndexedCollation to be quite useful:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    return [[[UILocalizedIndexedCollation currentCollation] sectionTitles] objectAtIndex:section];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
}
 
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:index];
}

https://developer.apple.com/documentation/uikit/uilocalizedindexedcollation

Solution 3 - Ios

I came up with an alternative approach to handling a single letter alphabet list without using sections. It's similar to Zaph's answer but instead of getting any value from returning a new index (since we'll always have 1 section), we calculate the index for the location of the first item in the array that begins with a certain character, then scroll to it.

The downside is this requires searching the array every time (is this absolutely terrible?), however I didn't notice any lag or slow behavior in the iOS simulator or on my iPhone 4S.

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
  return[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];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
  
  NSInteger newRow = [self indexForFirstChar:title inArray:self.yourStringArray];
  NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:newRow inSection:0];
  [tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionTop animated:NO];
  
  return index;
}

// Return the index for the location of the first item in an array that begins with a certain character
- (NSInteger)indexForFirstChar:(NSString *)character inArray:(NSArray *)array
{
  NSUInteger count = 0;
  for (NSString *str in array) {
    if ([str hasPrefix:character]) {
      return count;
    }
    count++;
  }
  return 0;
}

adding property to store last selected index like

 @property (assign, nonatomic) NSInteger previousSearchIndex;

and storing this property every time like:

- (NSInteger)indexForFirstChar:(NSString *)character inArray:(NSArray *)array
{
    NSUInteger count = 0;
    for (NSString *str in array) {
        if ([str hasPrefix:character]) {
            self.previousSearchIndex = count;
            return count;
        }
        count++;
    }
    return self.previousSearchIndex;
}

and updating scrollToRow code like:

 [tableView scrollToRowAtIndexPath:newIndexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];

Do this method even better and with nice animation.

Solution 4 - Ios

A bunch of people asked if it was possible to do this without sections. I wanted the same thing and I found a solution which might be a little shady and doesn't return a value to sectionForSectionIndexTitle but if you are in a corner and don't want to have to make a section for every letter of the alphabet this is a sure fix. Sorry to any code Nazis in advance. :P

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    if (thisTableDataIsShowing)
    {
        NSMutableArray *charactersForSort = [[NSMutableArray alloc] init];
        for (NSDictionary *item in d_itemsInTable)
        {
            if (![charactersForSort containsObject:[[item valueForKey:@"character_field_to_sort_by"] substringToIndex:1]])
            {
                [charactersForSort addObject:[[item valueForKey:@"character_field_to_sort_by"] substringToIndex:1]];
            }
        }
        return charactersForSort;
    }
    return nil;
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    BOOL found = NO;
    NSInteger b = 0;
    for (NSDictionary *item in d_itemsInTable)
    {
        if ([[[item valueForKey:@"character_field_to_sort_by"] substringToIndex:1] isEqualToString:title])
            if (!found)
            {
                [d_yourTableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:b inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:NO];
                found = YES;
            }
        b++;
    }
}

It works great if you are getting a large amount of data and sectioning it would take a bunch of work. :) Tried to use generic variables so you knew what I was doing. d_itemsInTable is an NSArray of NSDictionaries that I'm listing out to the UITableView.

Solution 5 - Ios

Here is a modified version of Kyle's function that handles the case of clicking an index for which you do not have a string:

- (NSInteger)indexForFirstChar:(NSString *)character inArray:(NSArray *)array
{
    char testChar = [character characterAtIndex:0];
    __block int retIdx = 0;
    __block int lastIdx = 0;
    
    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
        char firstChar = [obj characterAtIndex:0];
        
        if (testChar == firstChar) {
            retIdx = idx;
            *stop = YES;
        }
        
        //if we overshot the target, just use whatever previous one was
        if (testChar < firstChar) {
            retIdx = lastIdx;
            *stop = YES;
        }
        
        lastIdx = idx;
    }];
    return retIdx;
}

Solution 6 - Ios

If you're using a NSFetchedResultsController, you can just do:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [frc sectionIndexTitles];
}
 
- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [frc sectionForSectionIndexTitle:title atIndex:index];
}

Solution 7 - Ios

Implement the delegate methods -sectionIndexTitlesForTableView: and -tableView:sectionForSectionIndexTitle:atIndex:

See the UITableViewDataSource documentation for more info.

Solution 8 - Ios

Here's a simple solution in Swift, assuming you have your title headers in an array. If the title couldn't be found, it will return the previous index in the array.

func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
    return "ABCDEFGHIJKLMNOPQRSTUVWXYZ".characters.flatMap{String($0)}
}

func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
    return self.headerTitles.filter{$0 <= title}.count - 1
}

Solution 9 - Ios

If you're using MonoTouch, override the SectionIndexTitles(UITableView) method in the UITableViewDataSource class. Just return an array of strings and the subclass takes care of the rest.

class TableViewDataSource : UITableViewDataSource
{
  public override string[] SectionIndexTitles(UITableView tableView) 
  { 
    return new string[] { /*your string values */};
  }
}

*just a hint for those of us using C# and Mono (.NET) to write iPhone apps. :)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionduglaView Question on Stackoverflow
Solution 1 - IoszaphView Answer on Stackoverflow
Solution 2 - IosrwylandView Answer on Stackoverflow
Solution 3 - IosKyle CleggView Answer on Stackoverflow
Solution 4 - IoskrutView Answer on Stackoverflow
Solution 5 - IosDannyView Answer on Stackoverflow
Solution 6 - IosSnowmanView Answer on Stackoverflow
Solution 7 - IosAlex ReynoldsView Answer on Stackoverflow
Solution 8 - IosSamahView Answer on Stackoverflow
Solution 9 - IosbenhorgenView Answer on Stackoverflow