NSFetchedResultsController v.s. 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 для получения уведомлений об обновлениях. Похоже, что нет хорошего способа, чтобы эти два объекта работали хорошо.

Ответ 1

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

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

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

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

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

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

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

Ответ 2

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

- (NSString *)sectionInitial {

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

    return collRet;
}

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

Ответ 3

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

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

1) У нас есть DB с некоторыми данными, которые мы хотим извлечь и отобразить с помощью 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. И латинский капитал "А" равен 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 по умолчанию. TV делегат и методы источника данных:

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

Вот оно. Пока работает хорошо. Возможно, это сработает для вас.

Ответ 4

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

Просто замените "#" на "^" в ваших основных данных, чтобы разделы для вашего табличного представления были "A-Z ^". В то время как юникод "#" меньше "A", "^" - это как раз наоборот. Поэтому вам не трудно предсказать, что "^" будет следовать за 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];
}