iPhone UITableView. Как включить однобуквенный алфавитный список, как в музыкальном приложении?

82

В музыкальном приложении для iPhone при выборе «Исполнитель», «Песни» или «Альбомы» отображается tableView с вертикальным списком отдельных букв в правой части пользовательского интерфейса, который обеспечивает быструю прокрутку. Как включить эту функцию в моем приложении?

Привет, Дуг

дугла
источник

Ответы:

133

Предоставьте свои собственные индексные символы:

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

а потом:

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

Вам понадобятся разделы.

Zaph
источник
21
Есть ли способ иметь индексы без разделов (для плоского списка)?
ЕСФУЛ,
12
@ThEuSeFuL Вы можете иметь разделы без отображения заголовков разделов. Для конечного пользователя это будет выглядеть как плоский список.
Джон Халл
10
что означает "yourSectionIndexForTheSectionForSectionIndexTitle"?
ravoorinandan 03
2
@ravoorinandan по сути означает возврат индекса переменной заголовка в массиве заголовков. Итак, если заголовок @ "a", он должен вернуть 0. Если заголовок @ "i", он должен вернуть 2 и так далее. Вы можете сделать это, сделав массив доступным для обоих методов, показанных здесь, а затем вызвав [array indexOfObject: title] во втором методе. Хотя я бы сказал, что ответ ниже (связанный с UILocalizedIndexedCollation), вероятно, лучше.
Брайан Сачетта,
На всякий случай, кто-то совершил ту же ошибку, что и я, выберите tableView в раскадровке и измените цвет текста на желаемый. В моем случае цвет текста был автоматически выбран как прозрачный, поэтому индекс не отображался. Я потратил довольно много времени, выясняя, в чем проблема.
Vincent Joy
35

Еще вам нужно учесть локализацию разделов для каждого языка. Немного покопавшись, я обнаружил, UILocalizedIndexedCollationчто это весьма полезно:

- (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

Rwyland
источник
34

Я придумал альтернативный подход к работе с однобуквенным алфавитным списком без использования разделов. Он похож на ответ Zaph, но вместо того, чтобы получить какое-либо значение при возврате нового индекса (поскольку у нас всегда будет 1 раздел), мы вычисляем индекс для местоположения первого элемента в массиве, который начинается с определенного символа, а затем прокручиваем к нему.

Обратной стороной является необходимость поиска в массиве каждый раз (это ужасно?), Однако я не заметил каких-либо задержек или медленного поведения в симуляторе iOS или на моем 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;
}

добавление свойства для хранения последнего выбранного индекса, например

 @property (assign, nonatomic) NSInteger previousSearchIndex;

и сохраняя это свойство каждый раз, например:

- (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;
}

и обновление scrollToRowкода, например:

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

Сделайте этот метод еще лучше и с красивой анимацией.

Кайл Клегг
источник
14
проголосовали за то, что это означает, что мне не нужно было вводить алфавитный массив!
Поль Сезанн
1
хех. это тоже неплохое решение ... Apple приняла меня в лагерь какао, используя это в моем примере кода. означает ли это, что они официально одобряют это или нет ... я не знаю;).
Кайл Клегг
@PaulCezanne На самом деле, чтобы сделать его чище, я предлагаю вам перебрать ваш массив строк и взять первые буквы в верхнем регистре всех строк, чтобы ваш список был динамичным, и у вас не было Q, если у вас нет элементов, которые начинаются с буквы Q. Дайте мне знать, если вы хотите, чтобы я обновил свой код, чтобы показать это.
Кайл Клегг
Благодарю. На самом деле я сделал это в другом приложении, над которым работал. На практике это выглядело довольно странно. Массив AZ сбоку довольно нагляден. Когда у вас всего несколько букв, которые у вас будут, если у вас мало песен или вы отфильтровали много, назначение букв сбоку выглядит странным.
Поль Сезанн
1
@Mingebag Попробуйте отладить его, проверяя, какое значение indexForFirstChar возвращает каждый раз при его вызове. Каждый раз он должен быть от 0 до 25. Если он> 25, то, возможно, у вас есть небуквенные символы или что-то еще, из-за чего возвращается слишком высокое значение.
Кайл Клегг
21

Группа людей спрашивала, можно ли это сделать без разделов. Я хотел того же, и я нашел решение, которое может быть немного сомнительным и не возвращает значение sectionForSectionIndexTitle, но если вы находитесь в углу и не хотите создавать раздел для каждой буквы алфавита, это верное решение. Извините за любой код нацистов заранее. :П

- (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++;
    }
}

Он отлично работает, если вы получаете большой объем данных, и для его разбиения потребуется много работы. :) Пытался использовать общие переменные, чтобы вы знали, что я делаю. d_itemsInTable - это NSArray NSDictionaries, которые я перечисляю в UITableView.

круть
источник
1
Веселый факс: если вы вернете -1, он проигнорирует предупреждения компилятора и не попытается перейти к этому разделу из метода
sectionForSectionIndexTitle
Действительно отличный образец, спасибо, возможно, добавит тормоз после fund = YES; тоже было бы не так уж плохо ^^
Mingebag
3

Вот модифицированная версия функции Кайла, которая обрабатывает случай щелчка по индексу, для которого у вас нет строки:

- (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;
}
Дэнни
источник
3

Если вы используете a NSFetchedResultsController, вы можете просто сделать:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [frc sectionIndexTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [frc sectionForSectionIndexTitle:title atIndex:index];
}
Снеговик
источник
2

Реализуйте методы делегата -sectionIndexTitlesForTableView:и-tableView:sectionForSectionIndexTitle:atIndex:

См. UITableViewDataSourceДокументацию для получения дополнительной информации.

Алекс Рейнольдс
источник
2

Вот простое решение в Swift, если у вас есть заголовки заголовков в массиве. Если заголовок найти не удалось, он вернет предыдущий индекс в массиве.

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
}
Самах
источник
Я думаю, что предпочтительнее изменить вторую строку возврата, return self.headerTitles.count - self.headerTitles.filter{$0 > title}.countчтобы она возвращала индекс первой, а не последней совпадающей секции для данного заголовка индекса.
Chris C
На самом деле все зависит от того, чего ожидает клиент. Скорее всего, какой бы функционал вы ни выбрали, он будет прямо противоположным тому, что он хочет. :)
Samah
0

Если вы используете MonoTouch, переопределите метод SectionIndexTitles (UITableView) в классе UITableViewDataSource. Просто верните массив строк, а все остальное сделает подкласс.

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

* просто подсказка для тех из нас, кто использует C # и Mono (.NET) для написания приложений для iPhone. :)

Benhorgen
источник