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