Как использовать первый символ в качестве имени раздела

Я использую Core Data для представления таблицы, и я бы хотел использовать первую букву каждого из моих результатов в качестве заголовка раздела (так что я могу получить индекс раздела сбоку). Есть ли способ сделать это с помощью ключевого пути? Что-то вроде ниже, где я использую name.firstLetter как sectionNameKeyPath (к сожалению, это не работает).

Нужно ли вручную записывать первую букву каждого результата и создавать мои разделы? Лучше ли вставить новое свойство, чтобы просто удерживать первую букву и использовать ее как sectionNameKeyPath?

NSFetchedResultsController *aFetchedResultsController = 
[[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
            managedObjectContext:managedObjectContext
            sectionNameKeyPath:@"name.firstLetter"
            cacheName:@"Root"];

Спасибо.

** EDIT: ** Я не уверен, что это имеет значение, но мои результаты - японцы, отсортированные по Катакане. Я хочу использовать эту Катакану в качестве индекса раздела.

Ответ 1

Вы должны просто передать "имя" как sectionNameKeyPath. См. Этот ответ на вопрос "Основные данные, поддерживаемые UITableView с индексированием".

UPDATE
Это решение работает только в том случае, если вам просто нужно иметь быстрый скроллер заголовка индекса. В этом случае вы не будете отображать заголовки разделов. См. Ниже пример кода.

В противном случае я согласен с refulgentis, что переходное свойство является лучшим решением. Кроме того, при создании NSFetchedResultsController sectionNameKeyPath имеет это ограничение:

Если этот путь ключа не такой, как который указан в первом виде дескриптора в fetchRequest, они должны генерируют одинаковые относительные порядки. Например, первый дескриптор сортировки в fetchRequest может указывать ключ для постоянного свойства; sectionNameKeyPath может указывать ключ для переходного свойства, полученного из постоянное свойство.

Boilerplate UITableViewDataSource с использованием NSFetchedResultsController:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[fetchedResultsController sections] count];
}

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

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

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

// Don't implement this since each "name" is its own section:
//- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
//    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
//    return [sectionInfo name];
//}

ОБНОВЛЕНИЕ 2

Для нового свойства "uppercaseFirstLetterOfName" переходного процесса добавьте новый атрибут строки к применимому объекту в модели и проверьте поле "переходный".

Существует несколько способов реализации getter. Если вы создаете/создаете подклассы, вы можете добавить его в файл реализации подкласса (.m).

В противном случае вы можете создать категорию в NSManagedObject (я поставил это прямо вверху моего файла управления контроллером представления, но вы можете разделить его между собственным заголовком и файлом реализации):

@interface NSManagedObject (FirstLetter)
- (NSString *)uppercaseFirstLetterOfName;
@end

@implementation NSManagedObject (FirstLetter)
- (NSString *)uppercaseFirstLetterOfName {
    [self willAccessValueForKey:@"uppercaseFirstLetterOfName"];
    NSString *aString = [[self valueForKey:@"name"] uppercaseString];

    // support UTF-16:
    NSString *stringToReturn = [aString substringWithRange:[aString rangeOfComposedCharacterSequenceAtIndex:0]];

    // OR no UTF-16 support:
    //NSString *stringToReturn = [aString substringToIndex:1];

    [self didAccessValueForKey:@"uppercaseFirstLetterOfName"];
    return stringToReturn;
}
@end

Кроме того, в этой версии не забудьте передать 'uppercaseFirstLetterOfName' как sectionNameKeyPath:

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext
sectionNameKeyPath:@"uppercaseFirstLetterOfName" // this key defines the sections
cacheName:@"Root"];

И, чтобы раскомментировать tableView:titleForHeaderInSection: в реализации UITableViewDataSource:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo name];
}

Ответ 2

Там может быть более элегантный способ сделать это, но у меня недавно была та же проблема и вышло с этим решением.

Сначала я определил свойство переходного процесса для объектов, которые я индексировал, имя firstLetterOfName, и написал геттер в файл .m для объекта. e.x.

- (NSString *)uppercaseFirstLetterOfName {
    [self willAccessValueForKey:@"uppercaseFirstLetterOfName"];
    NSString *stringToReturn = [[self.name uppercaseString] substringToIndex:1];
    [self didAccessValueForKey:@"uppercaseFirstLetterOfName"];
    return stringToReturn;
}

Затем я настроил свой запрос/объекты выборки, чтобы использовать это свойство.

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Object" inManagedObjectContext:dataContext];
[request setEntity:entity];
[NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];

Боковое примечание, по поводу ничего: будьте осторожны с NSFetchedResultsController - он не совсем полностью испечен, но IMHO, и в любой ситуации, кроме простых случаев, перечисленных в документации, вам, вероятно, будет лучше делать это "старомодным" способом.

Ответ 4

Элегантный способ состоит в том, чтобы сделать "firstLetter" переходным свойством, ОДНАКО на практике это медленно.

Он медленный, потому что для вычисления переходного свойства весь объект должен быть поврежден в памяти. Если у вас много записей, это будет очень, очень медленно.

Быстрый, но неэлегантный способ заключается в создании непереходного свойства "firstLetter", которое вы обновляете каждый раз, когда вы устанавливаете свойство "name". Несколько способов сделать это: переопределить оценщик "setName:", переопределить "willSave", KVO.

Ответ 5

См. мой ответ на аналогичный вопрос здесь, в котором описывается, как создавать локализованные индексы sectionKey, которые сохраняются (поскольку вы не можете сортировать по переходным атрибутам в NSFetchedResultsController).