NSFetchedResultsController с разделами, созданными первой буквой строки - PullRequest
70 голосов
/ 11 июля 2009

Изучение основных данных на 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 ]

62 голосов
/ 11 июля 2009

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

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

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

    Создайте файлы классов для этого юридическое лицо.

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


// 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"];
54 голосов
/ 05 апреля 2012

Я думаю, что у меня есть еще один вариант, этот использует категорию на 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

15 голосов
/ 11 июля 2009

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

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

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

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

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

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

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

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

8 голосов
/ 13 августа 2012

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

- (NSString *)stringGroupByFirstInitial {
    NSString *temp = [self uppercaseString];
    if (!temp.length || temp.length == 1)
        return self;
    return [temp substringToIndex:1];
}
6 голосов
/ 09 декабря 2010

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

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

1 голос
/ 03 апреля 2017

Свифт 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)
        ]

И, наконец, Используйте ключ keyChar для sectionNameKeyPath

let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "lastname.firstChar", cacheName: "your_cache_name")
0 голосов
/ 30 марта 2012

Я думаю, у меня есть лучший способ сделать это. Вместо использования переходного свойства, в поле зрения появится. Пересчитайте производное свойство 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;
} 
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...