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

时间:2009-07-11 00:06:51

标签: iphone ios objective-c core-data

在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 个答案:

答案 0 :(得分:62)

Dave DeLong的方法很好,至少在我的情况下,只要你省略了几件事。以下是它对我的影响:

  • 添加新的可选字符串属性 到被称为的实体 “lastNameInitial”(或其他内容) 那个效果)。

    将此属性设为瞬态。这个 意味着核心数据不会打扰 将其保存到您的数据文件中。这个 属性只存在于内存中, 当你需要的时候。

    为此生成类文件 实体。

    不要担心这个问题 属性。创建这个getter(这是 一半魔术,恕我直言)


// 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"];

上一步:遵循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"];

答案 1 :(得分:54)

我想我还有另一种选择,这个选项在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 :(得分: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属性加载到内存中,并且是一组不同的姓氏首字母。

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

答案 3 :(得分:8)

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

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

答案 4 :(得分:6)

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

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

答案 5 :(得分:1)

swift 3

首先,创建NSString的扩展(因为CoreData基本上使用的是NSString)

extension NSString{
    func firstChar() -> String{
        if self.length == 0{
            return ""
        }
        return self.substring(to: 1)
    }
}

然后使用firstChar keypath排序,在我的例子中,lastname.firstChar

request.sortDescriptors = [
            NSSortDescriptor(key: "lastname.firstChar", ascending: true),
            NSSortDescriptor(key: "lastname", ascending: true),
            NSSortDescriptor(key: "firstname", ascending: true)
        ]

最后 使用sectionNameKeyPath

的firstChar keypath
let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "lastname.firstChar", cacheName: "your_cache_name")

答案 6 :(得分:-1)

我想我有更好的方法来做到这一点。而不是使用瞬态属性,在视图中将出现。重新计算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;
}