ios NSFetchedResultsController 由字符串的第一个字母创建的部分

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/1112521/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-30 16:09:10  来源:igfitidea点击:

NSFetchedResultsController with sections created by first letter of a string

iphoneiosobjective-ccore-data

提问by Greg Combs

Learning Core Data on the iPhone. There seem to be few examples on Core Data populating a table view with sections. The CoreDataBooksexample uses sections, but they're generated from full strings within the model. I want to organize the Core Data table into sections by the first letter of a last name, a la the Address Book.

在 iPhone 上学习核心数据。似乎很少有关于 Core Data 用部分填充表格视图的例子。该CoreDataBooks示例使用节,但他们是从模型中的满弦产生。我想按照姓氏的第一个字母将 Core Data 表组织成几个部分,就像地址簿一样。

I could go in and create another attribute, i.e. a single letter, for each person in order to act as the section division, but this seems kludgy.

我可以进入并为每个人创建另一个属性,即单个字母,以充当分区划分,但这似乎很笨拙。

Here's what I'm starting with ... the trick seems to be fooling the sectionNameKeyPath:

这是我开始的......这个技巧似乎在愚弄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"];
//....
}

采纳答案by Greg Combs

I think I've got yet another option, this one uses a category on NSString...

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

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

Now a little bit later on, while constructing your FRC:

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

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

This is now my favorite approach. Much cleaner/easier to implement. Moreover, you don't have to make any changes to your object model class to support it. This means that it'll work on any object model, provided the section name points to a property based on NSString

这是我现在最喜欢的方法。更清洁/更容易实施。此外,您不必对对象模型类进行任何更改来支持它。这意味着它可以在任何对象模型上工作,前提是部分名称指向基于 NSString 的属性

回答by Greg Combs

Dave DeLong's approach is good, at least in my case, as long as you omit a couple of things. Here's how it's working for me:

Dave DeLong 的方法很好,至少在我的情况下,只要您省略一些事情。这是它对我的工作方式:

  • Add a new optional string attribute to the entity called "lastNameInitial" (or something to that effect).

    Make this property transient. This means that Core Data won't bother saving it into your data file. This property will only exist in memory, when you need it.

    Generate the class files for this entity.

    Don't worry about a setter for this property. Create this getter (this is half the magic, IMHO)

  • 向名为“lastNameInitial”(或类似效果)的实体添加一个新的可选字符串属性。

    使这个属性成为瞬态。这意味着 Core Data 不会费心将其保存到您的数据文件中。这个属性只会在你需要的时候存在于内存中。

    为该实体生成类文件。

    不要担心这个属性的设置器。创建这个 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"];


PREVIOUSLY: Following Dave's initial steps to the letter generated issues where it dies upon setPropertiesToFetch with an invalid argument exception. I've logged the code and the debugging information below:

以前:按照 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"];

回答by Dave DeLong

Here's how you mightget it to work:

可以通过以下方式使其工作:

  • Add a new optional string attribute to the entity called "lastNameInitial" (or something to that effect).
  • Make this property transient. This means that Core Data won't bother saving it into your data file. This property will only exist in memory, when you need it.
  • Generate the class files for this entity.
  • Don't worry about a setter for this property. Create this getter (this is half the magic, IMHO)

    - (NSString *) lastNameInitial {
    [self willAccessValueForKey:@"lastNameInitial"];
    NSString * initial = [[self lastName] substringToIndex:1];
    [self didAccessValueForKey:@"lastNameInitial"];
    return initial;
    }
  • In your fetch request, request ONLY this PropertyDescription, like so (this is another quarter of the magic):

    NSDictionary * entityProperties = [myEntityDescription propertiesByName];
    NSPropertyDescription * lastNameInitialProperty = [entityProperties objectForKey:@"lastNameInitial"];
    [fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:lastNameInitialProperty]];
  • Make sure your fetch request ONLY returns distinct results (this is the last quarter of the magic):

    [fetchRequest setReturnsDistinctResults:YES];
  • Order your results by this letter:

    NSSortDescriptor * lastNameInitialSortOrder = [[[NSSortDescriptor alloc] initWithKey:@"lastNameInitial" ascending:YES] autorelease];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:lastNameInitialSortOrder]];
  • execute the request, and see what it gives you.

  • 向名为“lastNameInitial”(或类似效果)的实体添加一个新的可选字符串属性。
  • 使这个属性成为瞬态。这意味着 Core Data 不会费心将其保存到您的数据文件中。这个属性只会在你需要的时候存在于内存中。
  • 为该实体生成类文件。
  • 不要担心这个属性的设置器。创建这个 getter(这是一半的魔法,恕我直言)

    - (NSString *) lastNameInitial {
    [self willAccessValueForKey:@"lastNameInitial"];
    NSString * initial = [[self lastName] substringToIndex:1];
    [self didAccessValueForKey:@"lastNameInitial"];
    return initial;
    }
  • 在您的 fetch 请求中,只请求这个 PropertyDescription,就像这样(这是另一个神奇的地方):

    NSDictionary * entityProperties = [myEntityDescription propertiesByName];
    NSPropertyDescription * lastNameInitialProperty = [entityProperties objectForKey:@"lastNameInitial"];
    [fetchRequest setPropertiesToFetch:[NSArray arrayWithObject:lastNameInitialProperty]];
  • 确保您的 fetch 请求只返回不同的结果(这是魔法的最后一个季度):

    [fetchRequest setReturnsDistinctResults:YES];
  • 通过这封信订购您的结果:

    NSSortDescriptor * lastNameInitialSortOrder = [[[NSSortDescriptor alloc] initWithKey:@"lastNameInitial" ascending:YES] autorelease];
    [fetchRequest setSortDescriptors:[NSArray arrayWithObject:lastNameInitialSortOrder]];
  • 执行请求,看看它给了你什么。

If I understand how this works, then I'm guessing it will return an array of NSManagedObjects, each of which only has the lastNameInitial property loaded into memory, and who are a set of distinct last name initials.

如果我理解这是如何工作的,那么我猜它会返回一个 NSManagedObjects 数组,每个数组只将 lastNameInitial 属性加载到内存中,并且 who 是一组不同的姓氏首字母。

Good luck, and report back on how this works. I just made this up off the top of my head and want to know if this works. =)

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

回答by fawsha1

I like Greg Combs answer above. I've made a slight modification so that strings like "Smith" and "smith" can appear in the same section by converting the strings to upper case:

我喜欢上面的 Greg Combs 回答。我做了一点修改,通过将字符串转换为大写,像“Smith”和“smith”这样的字符串可以出现在同一部分:

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

回答by Mike Baldus

I encounter this issue all the time. The solution that seems best that i always come back to is to just give the entity a real first initial property. Being a real field provides for more efficient searching and ordering as you can set the field to indexed. It doesn't seem like it's too much work to pull the first initial out and populate a second field with it when the data is first imported / created. You have to write that initial parsing code either way, but you could do it once per entity and never again. The drawbacks seem to be you are storing one extra character per entity (and the indexing) really, that's likely insignificant.

我一直遇到这个问题。我总是回来的似乎最好的解决方案是给实体一个真正的第一个初始属性。作为一个真正的字段提供了更有效的搜索和排序,因为您可以将字段设置为索引。当第一次导入/创建数据时,将第一个首字母拉出并用它填充第二个字段似乎并没有太多工作。无论哪种方式,您都必须编写初始解析代码,但您可以为每个实体编写一次,以后再也不要这样做了。缺点似乎是您实际上为每个实体(和索引)存储了一个额外的字符,这可能无关紧要。

One extra note. I shy away from modifying the generated entity code. Maybe i'm missing something, but the tools for generating CoreData entities do not respect any code i might have put in there. Either option i pick when generating the code removes any customizations i might have made. If i fill up my entities with clever little functions, then i need to add a bunch of properties to that entity, i can't regenerate it easily.

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

回答by Benny Davidovitz

swift 3

迅捷 3

first, create extension to NSString (because CoreData is using basically NSString)

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

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

Then sort using firstChar keypath, in my case, lastname.firstChar

然后使用 firstChar 键路径进行排序,在我的例子中是 lastname.firstChar

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

And Finally Use the firstChar keypath for sectionNameKeyPath

最后使用 firstChar 键路径作为 sectionNameKeyPath

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

回答by user698333

I think I have a better way to do this. Instead of using transient property, in view will appear. Recalculate the derived property of the NSManagedObject and save the context.After the changes you can just reload the table view.

我想我有更好的方法来做到这一点。而不是使用瞬态属性,在视图中会出现。重新计算 NSManagedObject 的派生属性并保存上下文。更改后您可以重新加载表视图。

Here is an example of calculating the number of edges of each vertex, then sort the vertexes by the number of the edges. In this example, Capsid is vertex, touch is edge.

下面是一个计算每个顶点的边数的例子,然后按边数对顶点进行排序。在这个例子中,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;
}