NSFetchedResultsController vs UILocalizedIndexedCollation - PullRequest
14 голосов
/ 26 августа 2011

Я пытаюсь использовать 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 ]

6 голосов
/ 08 ноября 2012

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

  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) Китайский язык обычно сортируется по количеству штрихов или фонетическому произношению.

3 голосов
/ 16 февраля 2012

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

- (NSString *)sectionInitial {

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

    return collRet;
}

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

2 голосов
/ 04 ноября 2014

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

Просто замените «#» на «^» в ваших основных данных, чтобы разделы для вашего табличного представления были «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];
}
2 голосов
/ 23 марта 2013

Столкнувшись с той же проблемой в последнее время, я вынужден искать в сети (прежде всего, в стеке) поток подходящего решения, чтобы заставить 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. Для этого источника <a href="http://www.utf8-chartable.de/unicode-utf8-table.pl" rel="nofollow">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];
}

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

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...