NSFetchedResultsController vs UILocalizedIndexedCollation

Я пытаюсь использовать FRC со смешанными данными языка и хочу иметь индекс раздела.

Похоже, из документации вы должны иметь возможность переопределить FRC

- (NSString *)sectionIndexTitleForSectionName:(NSString *)sectionName
- (NSArray *)sectionIndexTitles

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

Кто-нибудь был в состоянии использовать FRC с UILocalizedIndexedCollation или мы вынуждены использовать метод ручной сортировки, упомянутый в примере UITableView + UILocalizedIndexedCollation (пример кода включен, где я получил эту работу).

Используя следующие свойства

@property (nonatomic, assign) UILocalizedIndexedCollation *collation;
@property (nonatomic, assign) NSMutableArray *collatedSections;

и код:

- (UILocalizedIndexedCollation *)collation
{
    if(collation == nil)
    {
        collation = [UILocalizedIndexedCollation currentCollation];
    }

    return collation;
}

- (NSArray *)collatedSections
{
    if(_collatedSections == nil)
    {
        int sectionTitlesCount = [[self.collation sectionTitles] count];

        NSMutableArray *newSectionsArray = [[NSMutableArray alloc] initWithCapacity:sectionTitlesCount];
        collatedSections = newSectionsArray;
        NSMutableArray *sectionsCArray[sectionTitlesCount];

        // Set up the sections array: elements are mutable arrays that will contain the time zones for that section.
        for(int index = 0; index < sectionTitlesCount; index++) 
        {
            NSMutableArray *array = [[NSMutableArray alloc] init];
            [newSectionsArray addObject:array];
            sectionsCArray[index] = array;
            [array release];
        }


        for(NSManagedObject *call in self.fetchedResultsController.fetchedObjects)
        {
            int section = [collation sectionForObject:call collationStringSelector:NSSelectorFromString(name)];
            [sectionsCArray[section] addObject:call];
        }

        NSArray *sortDescriptors = self.fetchedResultsController.fetchRequest.sortDescriptors;
        for(int index = 0; index < sectionTitlesCount; index++) 
        {
            [newSectionsArray replaceObjectAtIndex:index withObject:[sectionsCArray[index] sortedArrayUsingDescriptors:sortDescriptors]];
        }
    }
    return [[collatedSections retain] autorelease];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView 
{
    // The number of sections is the same as the number of titles in the collation.
    return [[self.collation sectionTitles] count];
}


- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section 
{
    // The number of time zones in the section is the count of the array associated with the section in the sections array.
    return [[self.collatedSections objectAtIndex:section] count];
}


- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section 
{
    if([[self.collatedSections objectAtIndex:section] count])
        return [[self.collation sectionTitles] objectAtIndex:section];
    return nil;
}


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


- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [self.collation sectionForSectionIndexTitleAtIndex:index];
}

Я хотел бы по-прежнему иметь возможность использовать протокол FRCDelegate, чтобы получать уведомления об обновлениях. Кажется, что нет хорошего способа заставить эти два объекта работать вместе.

4 ответа

Поскольку вы не можете сортировать по временному свойству, я реализовал решение...

  1. Создайте строковый атрибут с именем "sectionKey" для каждого сортируемого атрибута в каждой сущности в вашей модели базовых данных. Атрибут sectionKey будет вычисленным значением, полученным из базового атрибута (например, атрибута name или title). Это должно быть сохранено, потому что (в настоящее время) временное свойство не может использоваться в дескрипторе сортировки для запроса выборки. Включите индексирование для каждого атрибута sectionKey и base, для которого будет предлагаться сортировка. Чтобы применить это обновление к существующему приложению, вам необходимо выполнить упрощенную миграцию, а также включить подпрограмму для обновления уже существующих баз данных.

  2. Если вы заполняете данные (например, для заполнения новых установок стандартным набором данных или для создания локализованных баз данных SQLite для каждого целевого языка, из которых одна будет скопирована при первом запуске), в этом коде рассчитайте и обновите каждый Атрибут sectionKey объекта. Существуют разные мнения относительно "наилучшего" подхода к заполнению данных, однако стоит отметить, что несколько файлов plist для каждого языка (которые обычно варьируются от нескольких байтов до 20k, даже для списка, состоящего из нескольких сотен значений) оставят гораздо меньшая общая площадь, чем отдельная база данных SQLite для каждого языка (начинающаяся примерно с 20 тыс. каждый). Кроме того, Microsoft Excel для Mac можно настроить для обеспечения локальной сортировки списков, включив языковые функции (3).

  3. В конструкторе контроллера полученных результатов выполните сортировку по атрибуту sectionKey и base и передайте sectionKey для пути ключа имени раздела.

  4. Добавьте логику расчета для обновления атрибута (ов) sectionKey во всех добавляемых или редактируемых пользовательских вводах, например, в textFieldDidEndEditing:.

Это оно! Нет ручного разбиения извлеченных объектов на массив массивов. NSFetchedResultsController выполнит локализацию за вас. Например, в случае китайского (упрощенного) извлеченные объекты будут проиндексированы с помощью фонетического произношения (4).

(1) Из библиотеки разработчиков Apple IOS> Темы программирования интернационализации > Интернационализация и локализация. (2) 3_SimpleIndexedTableView для TableViewSuite. (3) Как включить функции китайского языка в Microsoft Office для Mac. (4) Китайский язык обычно сортируется по количеству штрихов или фонетическому произношению.

Брент, мое решение основано на FRC, и я получаю секционирование из выборки, указывающей временный атрибут для моего модельного объекта, который возвращает имя секции для объекта. Я использую UIlocalizedIndexedCollation только в реализации метода получения атрибута, тогда я полагаюсь на реализацию FRC на контроллере табличного представления. Конечно, я использую localizedCaseInsensitiveCompare в качестве селектора сортировки при получении.

- (NSString *)sectionInitial {

    NSInteger idx = [[UILocalizedIndexedCollation currentCollation] sectionForObject:self     collationStringSelector:@selector(localeName)];
    NSString *collRet = [[[UILocalizedIndexedCollation currentCollation] sectionTitles]     objectAtIndex:idx];

    return collRet;
}

Единственный недостаток, который у меня есть, это то, что я не могу иметь раздел # в конце, потому что я не изменяю сортировку из БД. Все остальное работает хорошо.

В последнее время я сталкиваюсь с той же проблемой, что заставляет меня искать в Интернете (прежде всего в стеке) поток подходящего решения, чтобы заставить NSFetchedResultsController (FRC) и UILocalizedIndexedCollation (LIC) работать вместе. Большинство найти решения не было достаточно хорошо, чтобы выполнить все требования. Важно отметить, что мы не можем использовать LIC для сортировки извлеченных объектов так, как это нужно, конечно, у нас будет огромная потеря производительности, а FRC не даст использовать все преимущества.

Итак, вот проблема в целом:

1) У нас есть БД с какими-то данными, которые мы хотим извлечь и отобразить с помощью FRC в списке (UITableView) с индексами (аналогично Contacts.app). Нам нужно передать ключ значения объекта, чтобы FRC мог принять решение о сортировке.

2) Даже если мы добавим специальное поле в наши модели CoreData для сортировки разделов и будем использовать заголовки индексов раздела FRC, мы не достигнем желаемого результата, курс FRC дает только найденные индексы, но не полный алфавит. Вдобавок к этому мы столкнемся с проблемой некорректного отображения индексов (не совсем уверен, почему так, может быть, какая-то ошибка в FRC). Например, в случае русского алфавита будут совершенно пустые или "странные" символы ($,?, ',…).

3) Если мы попытаемся использовать LIC для отображения хороших локализованных индексов, мы столкнемся с проблемой отображения разделов на основе данных в FRC для завершения локализованных "разделов" алфавита в LIC.

4) После того, как мы решили использовать LIC и каким-то образом решить проблему 3) мы заметим, что LIC поместит раздел "#" снизу (т. Е. Самый высокий индекс раздела), но FRC поместит "#"-подобные объекты сверху (т. Е. Самый низкий индекс раздела). - 0) Так будет иметь полное смещение разделов.

Приняв все это во внимание, я решил "обмануть" FRC без какого-либо большого "взлома", но заставить его сортировать данные так, как мне нужно (переместить все объекты из раздела "#" в конец списка).

Вот решение, к которому я пришел:

Я добавляю метод расширения к своему экземпляру NSManagedObject, чтобы подготовить имя сортировки, которое мы будем использовать в дескрипторе сортировки и пути ключа раздела для настройки FRC. Никаких специальных шагов не требуется, кроме тех, которые будут описаны ниже.

Проблема 4) возникает из-за алгоритмов сортировки FRC (низкоуровневый SQL), которые могут быть слегка изменены: только путем применения дескрипторов сортировки, которые больше зависят от ваших данных, предикатов и использования фиксированных предопределенных компараторов, которые не решают проблему.

Я заметил, что FRC решает, что символ "#" ниже, чем любой символ алфавита, противоположный LIC, где "#" самый высокий.

Логика FRC довольно проста, потому что символ "#" в UTF-8 - это U+0023. И латинская заглавная буква "A" - это U+0041, поэтому 23 < 41. Чтобы FRC поместил "#"-подобный объект в секцию с наивысшим индексом, нам нужно передать самый высокий символ UTF-8. Для этого источника http://www.utf8-chartable.de/unicode-utf8-table.pl этот символ UTF-8 равен U+1000FF (). Конечно, в реальной жизни этот символ практически не встречается. Давайте используем U+100000 для ясности.

Метод обновления имени сортировки выглядит примерно так:

#define UT8_MAX @"\U00100000"

- (void)updateSortName
{
    NSMutableString *prSortName = [NSMutableString stringWithString:[self dataDependantSortName]]; // for sort descriptors

    NSString *prSectionIdentifier = [[prSortName substringToIndex:1] uppercaseString]; // section keypath

    UILocalizedIndexedCollation *collation = [UILocalizedIndexedCollation currentCollation];

    NSUInteger sectionIndex = [collation sectionForObject:prSectionIdentifier collationStringSelector:@selector(stringValue)]; // stringValue is NSString category method that returns [NSString stringWithString:self]

    if(sectionIndex == [[collation sectionTitles] count] - 1) // last section tile '#'
    {
        prSectionIdentifier = UT8_MAX;
    }
    else
    {
        prSectionIdentifier = [collation sectionTitles][sectionIndex];
    }

    [prSortName replaceCharactersInRange:NSMakeRange(0, 1) withString:prSectionIdentifier];

//    sortName, sectionIdentifier - non-transient string attributes in CoreData model

    [self willChangeValueForKey:@"sortName"];
    [self setPrimitiveValue:prSortName forKey:@"sortName"];
    [self didChangeValueForKey:@"sortName"];

    [self willChangeValueForKey:@"sectionIdentifier"];
    [self setPrimitiveValue:prSectionIdentifier forKey:@"sectionIdentifier"];
    [self didChangeValueForKey:@"sectionIdentifier"];
}

Настройка FRC:

- (void)setupFRC
{
    NSEntityDescription *entityDescription =
    [NSEntityDescription entityForName:@"entity"
                inManagedObjectContext:self.moc];

    NSSortDescriptor *sortNameDescriptor = [[NSSortDescriptor alloc] initWithKey:@"sortName" ascending:YES selector:@selector(localizedCaseInsensitiveCompare:)]; // or any selector you need
    NSArray *sortDescriptors = [NSArray arrayWithObjects:sortNameDescriptor, nil];

    NSFetchRequest *fetchRequest = [NSFetchRequest new];
    [fetchRequest setEntity:entityDescription];
    [fetchRequest setFetchBatchSize:BATCH_SIZE];
    [fetchRequest setSortDescriptors:sortDescriptors];

    NSFetchedResultsController *fetchedResultsController =
    [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
                                        managedObjectContext:self.moc
                                          sectionNameKeyPath:@"sectionIdentifier"
                                                   cacheName:nil];
    self.fetchedResultsController = fetchedResultsController;
}

Методы делегата FRC по умолчанию. ТВ делегат и методы источника данных:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{
    return [[self localizedIndexedCollation] sectionTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index
{
    NSString *indexTitle = [title isEqualToString:@"#"] ? UT8_MAX : title;
    NSInteger fetchTitleIndex = NSNotFound;

    NSArray *sections = [self.fetchedResultsController sections];
    for (id <NSFetchedResultsSectionInfo> sectionInfo in sections)
    {
        if([[sectionInfo name] isEqualToString:indexTitle])
        {
            fetchTitleIndex = [sections indexOfObject:sectionInfo];
            break;
        }
    }

    return fetchTitleIndex;
}

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

    NSInteger collationTitleIndex = [[self localizedIndexedCollation] sectionForObject:fetchTitle
                                                               collationStringSelector:@selector(stringValue)];
    return [[[self localizedIndexedCollation] sectionTitles] objectAtIndex:collationTitleIndex];
}

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

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

Это оно. Пока работает хорошо. Может быть, это будет работать для вас.

Я нашел простой способ решить эту проблему!

Просто замените "#" на "^" в ваших основных данных, чтобы разделы для вашего табличного представления были "AZ^". В то время как юникод '#' меньше, чем 'A', '^' s как раз наоборот. Так что вам нетрудно предсказать, что '^' будет следовать за Z в ваших разделах.

Затем вы должны заменить разделы выбранного контроллера результатов. просто с помощью этой пары строк кода:

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView
{

    NSMutableArray *array = [[NSMutableArray alloc] initWithArray:[self.frc sectionIndexTitles]];

    // If "^" is in the section, replace it to "#"
    if ( [[array lastObject] isEqualToString:@"^"])
    {
        [array setObject:@"#" atIndexedSubscript:[array count]-1];
        return array;
    }
    // If "#" is not in the section
    return [self.frc sectionIndexTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title
               atIndex:(NSInteger)index
{
    if ([title isEqualToString:@"#"]) {
        return [self.frc sectionForSectionIndexTitle:@"^" atIndex:index];
    }
    return [self.frc sectionForSectionIndexTitle:title atIndex:index];
}

-(NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
{
    if ([[[self.frc sectionIndexTitles] objectAtIndex:section] isEqualToString:@"^"]) {
        return @"#";
    }
    return [[self.frc sectionIndexTitles] objectAtIndex:section];
}
Другие вопросы по тегам