NSFetchedResultsController,包含由字符串的第一个字母创建的部分

Gre*_*mbs 70 iphone core-data objective-c ios

在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"];
//....
}
Run Code Online (Sandbox Code Playgroud)

Gre*_*mbs 62

Dave DeLong的方法很好,至少在我的情况下,只要你省略了几件事.以下是它如何为我工作:

  • 将新的可选字符串属性添加到名为"lastNameInitial"的实体(或其他类似的效果).

    使此属性瞬态.这意味着Core Data不会将其保存到您的数据文件中.当您需要时,此属性将仅存在于内存中.

    生成此实体的类文件.

    不要担心这个属性的setter.创造这个吸气剂(这是神奇的一半,恕我直言)


// 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"];
Run Code Online (Sandbox Code Playgroud)

以前:遵循Dave的初始步骤生成的问题,它在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"];
Run Code Online (Sandbox Code Playgroud)

  • 你如何避免"在实体中找不到密钥路径X"?你还要把它放在模型设计器文件中吗? (3认同)

Gre*_*mbs 54

我想我还有另一种选择,这个选项在NSString上使用了一个类别......

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

现在稍后,在构建你的FRC时:

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

这是我最喜欢的方法.更清洁/更容易实施.此外,您不必对对象模型类进行任何更改即可支持它.这意味着它将适用于任何对象模型,前提是节名称指向基于NSString的属性

  • 请注意,如果您有非常多的行/对象,则类别方法中的此伪瞬态属性将导致每个FRC提取速度比您按模型上的现有实际属性进行分组时要慢几个数量级.(非常大,我的意思是成千上万行). (6认同)
  • 只是一个重要提示:当使用此解决方案并设置排序描述时,您将获得不同的Characaters(A或a),使用选择器:`selector:@selector(localizedCaseInsensitiveCompare:)`在排序描述符中使用.然后你不应该得到警告`索引14处的获取对象有一个乱序的部分名称'P. 对象必须按部分名称'`排序 (3认同)

Dav*_*ong 15

以下是您可以如何使用它:

  • 将新的可选字符串属性添加到名为"lastNameInitial"的实体(或其他类似的效果).
  • 使此属性瞬态.这意味着Core Data不会将其保存到您的数据文件中.当您需要时,此属性将仅存在于内存中.
  • 生成此实体的类文件.
  • 不要担心这个属性的setter.创造这个吸气剂(这是神奇的一半,恕我直言)

    - (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数组,每个NSManagedObjects只有lastNameInitial属性加载到内存中,并且是一组不同的姓氏首字母.

祝你好运,并报告这是如何运作的.我只是把它从头顶上做起来想知道这是否有效.=)


小智 8

我喜欢Greg Combs上面的回答.我做了一些修改,以便通过将字符串转换为大写字母来使"Smith"和"smith"等字符串出现在同一节中:

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


小智 6

我一直遇到这个问题.我总是回到最好的解决方案是给实体一个真正的第一个初始属性.作为一个真实的字段提供更有效的搜索和排序,因为您可以将字段设置为索引.在首次导入/创建数据时,将第一个初始值拉出并用它填充第二个字段似乎并不是太多工作.您必须以任何方式编写初始解析代码,但是您可以为每个实体执行一次,而不是再次执行.缺点似乎是你每个实体(和索引)真正存储一个额外的字符,这可能是微不足道的.

一个额外的说明.我回避修改生成的实体代码.也许我错过了一些东西,但是用于生成CoreData实体的工具并不尊重我可能放在那里的任何代码.我生成代码时选择的任一选项都会删除我可能进行的任何自定义.如果我用聪明的小函数填充我的实体,那么我需要向该实体添加一堆属性,我无法轻松地重新生成它.

  • 我通过保持核心数据生成的文件原始来解决这个问题,然后简单地为这些类的任何其他辅助方法创建类别. (4认同)