NSFetchedResultsController с разделами, созданными первой буквой строки

Изучение основных данных на iPhone. Кажется, что в Core Data несколько примеров заполняются табличным представлением с разделами. В примере CoreDataBooks используются разделы, но они генерируются из полных строк в модели. Я хочу организовать таблицу основных данных в разделы по первой букве фамилии, а также адресной книге.

Я мог бы войти и создать другой атрибут, т.е. одну букву, для каждого человека, чтобы действовать как разделение разделов, но это кажется kludgy.

Вот что я начинаю с... трюк, кажется, обманывает sectionNameKeyPath:

- (NSFetchedResultsController *)fetchedResultsController {
//.........SOME STUFF DELETED
    // Edit the sort key as appropriate.
    NSSortDescriptor *orderDescriptor = [[NSSortDescriptor alloc] initWithKey:@"personName" ascending:YES];
    NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:orderDescriptor, nil];

    [fetchRequest setSortDescriptors:sortDescriptors];
    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = 
            [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest 
            managedObjectContext:managedObjectContext 
            sectionNameKeyPath:@"personName" cacheName:@"Root"];
//....
}

Ответ 1

Я думаю, что у меня есть еще один вариант, который использует категорию в NSString...

@implementation NSString (FetchedGroupByString)
- (NSString *)stringGroupByFirstInitial {
    if (!self.length || self.length == 1)
        return self;
    return [self substringToIndex:1];
}
@end

Теперь немного позже, при построении FRC:

- (NSFetchedResultsController *)newFRC {
    NSFetchedResultsController *frc = [[NSFetchedResultsController alloc] initWithFetchRequest:awesomeRequest
            managedObjectContext:coolManagedObjectContext
            sectionNameKeyPath:@"lastName.stringGroupByFirstInitial"
            cacheName:@"CoolCat"];
    return frc;
}

Теперь это мой любимый подход. Гораздо чище/проще реализовать. Кроме того, вам не нужно вносить какие-либо изменения в класс объектной модели для его поддержки. Это означает, что он будет работать на любой объектной модели, если имя раздела указывает на свойство, основанное на NSString

Ответ 2

Дейв DeLong подход хорош, по крайней мере, в моем случае, если вы опустите пару вещей. Вот как это работает для меня:

  • Добавить новый необязательный строковый атрибут к объекту, названному "lastNameInitial" (или что-то этот эффект).

    Сделайте это свойство кратковременным. Эта означает, что Core Data не беспокоит сохраняя его в файле данных. Эта свойство будет существовать только в памяти, когда вам это нужно.

    Создайте файлы классов для этого объект.

    Не беспокойтесь о сеттере для этого имущество. Создайте этот геттер (это половина магии, ИМХО)


// THIS ATTRIBUTE GETTER GOES IN YOUR OBJECT MODEL
- (NSString *) committeeNameInitial {
    [self willAccessValueForKey:@"committeeNameInitial"];
    NSString * initial = [[self committeeName] substringToIndex:1];
    [self didAccessValueForKey:@"committeeNameInitial"];
    return initial;
}


// THIS GOES IN YOUR fetchedResultsController: METHOD
// Edit the sort key as appropriate.
NSSortDescriptor *nameInitialSortOrder = [[NSSortDescriptor alloc] 
        initWithKey:@"committeeName" ascending:YES];

[fetchRequest setSortDescriptors:[NSArray arrayWithObject:nameInitialSortOrder]];

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

ПРЕДЫДУЩАЯ: После первых шагов Дейва к письму возникли проблемы, когда он умирает при установке setPropertiesToFetch с недопустимым исключением аргументов. Я зарегистрировал код и информацию для отладки ниже:

NSDictionary * entityProperties = [entity propertiesByName];
NSPropertyDescription * nameInitialProperty = [entityProperties objectForKey:@"committeeNameInitial"];
NSArray * tempPropertyArray = [NSArray arrayWithObject:nameInitialProperty];

//  NSARRAY * tempPropertyArray RETURNS:
//    <CFArray 0xf54090 [0x30307a00]>{type = immutable, count = 1, values = (
//    0 : (<NSAttributeDescription: 0xf2df80>), 
//    name committeeNameInitial, isOptional 1, isTransient 1,
//    entity CommitteeObj, renamingIdentifier committeeNameInitial, 
//    validation predicates (), warnings (), versionHashModifier (null), 
//    attributeType 700 , attributeValueClassName NSString, defaultValue (null)
//    )}

//  NSInvalidArgumentException AT THIS LINE vvvv
[fetchRequest setPropertiesToFetch:tempPropertyArray];

//  *** Terminating app due to uncaught exception 'NSInvalidArgumentException',
//    reason: 'Invalid property (<NSAttributeDescription: 0xf2dfb0>), 
//    name committeeNameInitial, isOptional 1, isTransient 1, entity CommitteeObj, 
//    renamingIdentifier committeeNameInitial, 
//    validation predicates (), warnings (), 
//    versionHashModifier (null), 
//    attributeType 700 , attributeValueClassName NSString, 
//    defaultValue (null) passed to setPropertiesToFetch: (property is transient)'

[fetchRequest setReturnsDistinctResults:YES];

NSSortDescriptor * nameInitialSortOrder = [[[NSSortDescriptor alloc]
    initWithKey:@"committeeNameInitial" ascending:YES] autorelease];

[fetchRequest setSortDescriptors:[NSArray arrayWithObject:nameInitialSortOrder]];

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

Ответ 3

Вот как вы можете заставить его работать:

  • Добавьте новый необязательный строковый атрибут к объекту, называемому "lastNameInitial" (или что-то в этом роде).
  • Сделать это свойство кратковременным. Это означает, что Core Data не позаботится о сохранении его в вашем файле данных. Это свойство будет существовать только в памяти, когда вам это нужно.
  • Сгенерировать файлы классов для этого объекта.
  • Не беспокойтесь об установщике этого свойства. Создайте этот геттер (это половина волшебства, IMHO)

    - (NSString *) lastNameInitial {
    [self willAccessValueForKey:@"lastNameInitial"];
    NSString * initial = [[self lastName] substringToIndex:1];
    [self didAccessValueForKey:@"lastNameInitial"];
    return initial;
    }
  • В запросе на выбор запросите ТОЛЬКО этот PropertyDescription, например (это еще одна четверть магии):

    NSDictionary * entityProperties = [myEntityDescription propertiesByName];
    NSPropertyDescription * lastNameInitialProperty = [entityProperties objectForKey:@"lastNameInitial"];
    [fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:lastNameInitialProperty]];
  • Убедитесь, что ваш запрос на выборку ТОЛЬКО возвращает отличные результаты (это последняя четверть магии):

    [fetchRequest setReturnsDistinctResults:YES];
  • Закажите ваши результаты по этому письму:

    NSSortDescriptor * lastNameInitialSortOrder = [[[NSSortDescriptor alloc] initWithKey:@"lastNameInitial" ascending:YES] autorelease];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:lastNameInitialSortOrder]];
  • выполнить запрос и посмотреть, что он вам дает.

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

Удачи, и сообщите о том, как это работает. Я просто сделал это с головы до головы и хочу знать, работает ли это. =)

Ответ 4

Мне нравится ответ Грега Комбса выше. Я сделал небольшую модификацию, так что строки, подобные "Смит" и "кузнец", могут отображаться в том же разделе, преобразуя строки в верхний регистр:

- (NSString *)stringGroupByFirstInitial {
    NSString *temp = [self uppercaseString];
    if (!temp.length || temp.length == 1)
        return self;
    return [temp substringToIndex:1];
}

Ответ 5

Я постоянно сталкиваюсь с этой проблемой. Решение, которое кажется лучшим, что я всегда возвращаю, - это просто дать сущности реальное первое начальное свойство. Быть реальным полем обеспечивает более эффективный поиск и упорядочение, так как вы можете установить поле для индексации. Не похоже, что слишком много работы, чтобы вытащить первый начальный элемент и заполнить второе поле, когда данные сначала импортируются/создаются. Вы должны написать этот исходный код синтаксического анализа в любом случае, но вы можете сделать это один раз для объекта и никогда больше. Недостатки, похоже, в том, что вы сохраняете один дополнительный символ на сущность (и индексирование) на самом деле, что, вероятно, незначительно.

Еще одно примечание. Я уклоняюсь от изменения кода сгенерированного объекта. Может быть, я что-то пропустил, но инструменты для создания объектов CoreData не уважают никакого кода, который я мог бы там добавить. Любая опция, которую я выбираю при генерации кода, удаляет любые настройки, которые я мог бы сделать. Если я пополняю свои объекты умными небольшими функциями, тогда мне нужно добавить кучу свойств к этому объекту, я не могу его легко восстановить.

Ответ 6

swift 3

сначала создайте расширение для NSString (потому что CoreData использует в основном NSString)

extension NSString{
    func firstChar() -> String{
        if self.length == 0{
            return ""
        }
        return self.substring(to: 1)
    }
}

Затем выполните сортировку с использованием keypath firstChar, в моем случае, lastname.firstChar

request.sortDescriptors = [
            NSSortDescriptor(key: "lastname.firstChar", ascending: true),
            NSSortDescriptor(key: "lastname", ascending: true),
            NSSortDescriptor(key: "firstname", ascending: true)
        ]

И наконец Используйте путь для первого ключа для секцииNameKeyPath

let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "lastname.firstChar", cacheName: "your_cache_name")

Ответ 7

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

Вот пример вычисления количества ребер каждой вершины, затем отсортируйте вершины по числу ребер. В этом примере Capsid является вершиной, касанием является край.

- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
    [self.tableView endUpdates];
    [self.tableView reloadData];
}

- (void)viewWillAppear:(BOOL)animated
{
    [super viewWillAppear:animated];
    NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:@"Capsid"];
    NSError *error = nil;
    NSArray *results = [self.managedObjectContext executeFetchRequest:request error:&error];
    if (error) {
        NSLog(@"refresh error");
        abort();
    }
    for (Capsid *capsid in results) {
        unsigned long long sum = 0;
        for (Touch *touch in capsid.vs) {
            sum += touch.count.unsignedLongLongValue;
        }
        for (Touch *touch in capsid.us) {
            sum += touch.count.unsignedLongLongValue;
        }
        capsid.sum = [NSNumber numberWithUnsignedLongLong:sum];
    }
    if (![self.managedObjectContext save:&error]) {
        NSLog(@"save error");
        abort();
    }
}

- (NSFetchedResultsController *)fetchedResultsController
{
    if (__fetchedResultsController != nil) {
        return __fetchedResultsController;
    }

    // Set up the fetched results controller.
    // Create the fetch request for the entity.
    NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init];
    // Edit the entity name as appropriate.
    NSEntityDescription *entity = [NSEntityDescription entityForName:@"Capsid" inManagedObjectContext:self.managedObjectContext];
    [fetchRequest setEntity:entity];

    // Set the batch size to a suitable number.
    [fetchRequest setFetchBatchSize:20];

    // Edit the sort key as appropriate.
    //    NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"timeStamp" ascending:NO];
//    NSSortDescriptor *sumSortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"sum" ascending:NO];
//    NSArray *sortDescriptors = [NSArray arrayWithObjects:sumSortDescriptor, nil];
    [fetchRequest setReturnsDistinctResults:YES];
    NSSortDescriptor *sortDescriptor = [NSSortDescriptor sortDescriptorWithKey:@"sum" ascending:NO];
    NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
    [fetchRequest setSortDescriptors:sortDescriptors];


    // Edit the section name key path and cache name if appropriate.
    // nil for section name key path means "no sections".
    NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil];
    aFetchedResultsController.delegate = self;
    self.fetchedResultsController = aFetchedResultsController;

    NSError *error = nil;
    if (![self.fetchedResultsController performFetch:&error]) {
        /*
         Replace this implementation with code to handle the error appropriately.

         abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development. 
         */
        NSLog(@"Unresolved error %@, %@", error, [error userInfo]);
        abort();
    }

    return __fetchedResultsController;
}