如何使用第一个字符作为节名称

时间:2009-11-16 09:39:56

标签: iphone objective-c cocoa-touch uitableview core-data

我正在使用Core Data作为表格视图,我想使用每个结果的第一个字母作为节标题(所以我可以在侧面获得节索引)。有没有办法用关键路径做到这一点?类似于下面的内容,我使用name.firstLetter作为sectionNameKeyPath(遗憾的是,这不起作用)。

我是否必须手动抓取每个结果的第一个字母并创建我的部分?放入一个新属性来保存第一个字母并将其用作sectionNameKeyPath更好吗?

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

感谢。

**编辑:**我不确定它是否有所作为,但我的结果是日语,按片假名排序。我想用这些片假名作为部分索引。

6 个答案:

答案 0 :(得分:79)

您应该只将“name”作为sectionNameKeyPath传递。请参阅此answer以查询“使用索引编制核心数据支持的UITableView”。

<强>更新
只有你只关心快速索引标题滚动条时,该解决方案才有效。在这种情况下,您不会显示节标题。请参阅下面的示例代码。

否则,我同意reffgentis,即瞬态属性是最佳解决方案。此外,在创建NSFetchedResultsController时,sectionNameKeyPath具有此限制:

  

如果此键路径不相同   由第一种排序指定的   fetchRequest中的描述符,它们必须   生成相同的相对排序。   例如,第一个排序描述符   在fetchRequest中可以指定密钥   持久性财产;   sectionNameKeyPath可能指定一个键   对于从中派生的瞬态属性   持久性属性。

使用NSFetchedResultsController的Boilerplate UITableViewDataSource实现:

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
    return [[fetchedResultsController sections] count];
}

- (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
    return [fetchedResultsController sectionIndexTitles];
}

- (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
    return [fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo numberOfObjects];
}

// Don't implement this since each "name" is its own section:
//- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
//    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
//    return [sectionInfo name];
//}

更新2

对于新的“uppercaseFirstLetterOfName”瞬态属性,将新的字符串属性添加到模型中的适用实体,并选中“瞬态”框。

有几种方法可以实现getter。如果要生成/创建子类,则可以将其添加到子类的实现(.m)文件中。

否则,你可以在NSManagedObject上创建一个类别(我把它放在我的视图控制器的实现文件的顶部,但你可以在它自己的正确的头文件和实现文件之间拆分):

@interface NSManagedObject (FirstLetter)
- (NSString *)uppercaseFirstLetterOfName;
@end

@implementation NSManagedObject (FirstLetter)
- (NSString *)uppercaseFirstLetterOfName {
    [self willAccessValueForKey:@"uppercaseFirstLetterOfName"];
    NSString *aString = [[self valueForKey:@"name"] uppercaseString];

    // support UTF-16:
    NSString *stringToReturn = [aString substringWithRange:[aString rangeOfComposedCharacterSequenceAtIndex:0]];

    // OR no UTF-16 support:
    //NSString *stringToReturn = [aString substringToIndex:1];

    [self didAccessValueForKey:@"uppercaseFirstLetterOfName"];
    return stringToReturn;
}
@end

此外,在此版本中,不要忘记将'uppercaseFirstLetterOfName'作为sectionNameKeyPath传递:

NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:managedObjectContext
sectionNameKeyPath:@"uppercaseFirstLetterOfName" // this key defines the sections
cacheName:@"Root"];

并且,要在UITableViewDataSource实现中取消注释tableView:titleForHeaderInSection:

- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
    id <NSFetchedResultsSectionInfo> sectionInfo = [[fetchedResultsController sections] objectAtIndex:section];
    return [sectionInfo name];
}

答案 1 :(得分:11)

可能有一种更优雅的方式来做到这一点,但我最近遇到了同样的问题,并提出了这个解决方案。

首先,我在我正在索引的对象上定义了一个名为firstLetterOfName的瞬态属性,并将getter写入该对象的.m文件中。 e.x。

- (NSString *)uppercaseFirstLetterOfName {
    [self willAccessValueForKey:@"uppercaseFirstLetterOfName"];
    NSString *stringToReturn = [[self.name uppercaseString] substringToIndex:1];
    [self didAccessValueForKey:@"uppercaseFirstLetterOfName"];
    return stringToReturn;
}

接下来,我设置我的获取请求/实体以使用此属性。

NSFetchRequest *request = [[NSFetchRequest alloc] init];
NSEntityDescription *entity = [NSEntityDescription entityForName:@"Object" inManagedObjectContext:dataContext];
[request setEntity:entity];
[NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:@"name" ascending:YES selector:@selector(caseInsensitiveCompare:)];
NSArray *sortDescriptors = [NSArray arrayWithObject:sortDescriptor];
[request setSortDescriptors:sortDescriptors];

旁注,没有任何结果:小心使用NSFetchedResultsController - 它还没有完全被烘焙,但恕我直言,除了文档中列出的简单案例之外的任何情况,你可能会以“老式”的方式做得更好。

答案 2 :(得分:4)

我使用NSFetchedResultsController v.s. UILocalizedIndexedCollation问题中提到的UILocalizedIndexCollat​​ion解决了这个问题

答案 3 :(得分:1)

优雅的方法是让“firstLetter”成为一种短暂的属性,但在实践中却很慢。

它很慢,因为要计算瞬态属性,整个对象需要出错才能进入内存。如果你有很多记录,它会非常非常慢。

快速但不优雅的方法是创建一个非瞬态“firstLetter”属性,每次设置“name”属性时都会更新该属性。有几种方法:覆盖“setName:”评估者,覆盖“willSave”,KVO。

答案 4 :(得分:1)

请参阅我对类似问题here的回答,其中我描述了如何创建持久化的本地化sectionKey索引(因为您无法对NSFetchedResultsController中的瞬态属性进行排序)。

答案 5 :(得分:0)

这是obj-c的简单解决方案,几年和一种语言迟到。它在我的项目中工作和运作很快。

首先,我在NSString上创建了一个类别,命名文件NSString + Indexing。我写了一个返回字符串

的第一个字母的方法
@implementation NSString (Indexing)

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

然后我在fetched result controller的定义中使用了这个方法,如下所示

_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:@"name.stringGroupByFirstInitial"
                                                                               cacheName:nil];

以上代码与两个stringIndex方法结合使用,无需弄乱瞬态属性或存储仅包含核心数据中第一个字母的单独属性

然而,当我尝试用Swift做同样的事情时,它抛出异常,因为它不喜欢将函数作为部分键路径(或者计算出的字符串属性 - 我也尝试过)如果有人知道如何实现在Swift中同样的事情我很想知道如何。