NSFetchedResultsController с разделами, созданными по первой букве строки
Изучение основных данных на iPhone. Кажется, есть несколько примеров, когда Базовые Данные заполняют табличное представление разделами. В примере CoreDataBooks используются разделы, но они генерируются из полных строк в модели. Я хочу организовать таблицу базовых данных по разделам по первой букве фамилии, в виде адресной книги.
Я мог бы пойти и создать другой атрибут, то есть одну букву, для каждого человека, чтобы он действовал как разделение на секции, но это кажется глупым.
Вот то, что я начинаю с... трюк, кажется, обманывает 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"];
//....
}
7 ответов
Я думаю, что у меня есть еще один вариант, этот использует категорию на 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
Подход Дэйва Делонга хорош, по крайней мере, в моем случае, если вы пропустите пару вещей. Вот как это работает для меня:
Добавьте новый необязательный строковый атрибут к сущности с именем "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"];
Вот как вы можете заставить его работать:
- Добавьте новый необязательный строковый атрибут к сущности с именем "lastNameInitial" (или что-то в этом роде).
- Сделайте это свойство временным. Это означает, что Core Data не потрудится сохранить его в файл данных. Это свойство будет существовать только в памяти, когда вам это нужно.
- Создайте файлы классов для этого объекта.
Не беспокойтесь о сеттере для этого свойства. Создайте этот геттер (это половина волшебства, ИМХО)
- (NSString *) lastNameInitial {
[self willAccessValueForKey: @ "lastNameInitial"];
NSString * initial = [[self lastName] substringToIndex: 1];
[self didAccessValueForKey: @ "lastNameInitial"];
вернуть начальный;
}В вашем запросе на выборку запросите ТОЛЬКО это 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, загруженное в память, и которые представляют собой набор различных инициалов фамилии.
Удачи, и доложите, как это работает. Я только что сделал это с макушки головы и хочу знать, работает ли это. знак равно
Мне нравится Грег Комбс ответ выше. Я сделал небольшую модификацию, чтобы строки, такие как "Smith" и "Smith", могли появляться в одном и том же разделе путем преобразования строк в верхний регистр:
- (NSString *)stringGroupByFirstInitial {
NSString *temp = [self uppercaseString];
if (!temp.length || temp.length == 1)
return self;
return [temp substringToIndex:1];
}
Я сталкиваюсь с этой проблемой все время. Лучшее решение, к которому я всегда возвращаюсь, состоит в том, чтобы дать сущности реальное первое начальное свойство. Быть реальным полем обеспечивает более эффективный поиск и упорядочение, так как вы можете установить индексированное поле. Не похоже, что это слишком много работы, чтобы вытащить первый инициал и заполнить его вторым полем, когда данные впервые импортируются / создаются. Вы должны написать этот исходный код синтаксического анализа в любом случае, но вы можете сделать это один раз для объекта и никогда больше. Недостатки, по-видимому, в том, что вы сохраняете по одному дополнительному символу на сущность (и индексацию), что, вероятно, незначительно.
Одна дополнительная заметка. Я уклоняюсь от модификации сгенерированного кода объекта. Может быть, я что-то упускаю, но инструменты для создания сущностей CoreData не учитывают какой-либо код, который я мог бы вставить туда. Любая опция, которую я выбираю при создании кода, удаляет любые настройки, которые я мог сделать. Если я заполняю свои сущности умными маленькими функциями, то мне нужно добавить кучу свойств к этой сущности, я не могу легко восстановить ее.
Свифт 3
во-первых, создайте расширение для NSString (потому что CoreData использует в основном NSString)
extension NSString{
func firstChar() -> String{
if self.length == 0{
return ""
}
return self.substring(to: 1)
}
}
Затем сортируйте, используя keyCath firstChar, в моем случае, lastname.firstChar
request.sortDescriptors = [
NSSortDescriptor(key: "lastname.firstChar", ascending: true),
NSSortDescriptor(key: "lastname", ascending: true),
NSSortDescriptor(key: "firstname", ascending: true)
]
И наконец, используйте keyCath firstChar для sectionNameKeyPath
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "lastname.firstChar", cacheName: "your_cache_name")
Я думаю, у меня есть лучший способ сделать это. Вместо использования переходного свойства, в поле зрения появится. Пересчитайте производное свойство NSManagedObject и сохраните контекст. После внесенных изменений вы можете просто перезагрузить табличное представление.
Вот пример вычисления количества ребер каждой вершины, затем сортировка вершин по количеству ребер. В этом примере Capsid - это вершина, touch - это край.
- (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;
}