NSFetchedResultsController with sections created by first letter of a string

后端 未结 7 1454
再見小時候
再見小時候 2020-11-28 01:09

Learning Core Data on the iPhone. There seem to be few examples on Core Data populating a table view with sections. The CoreDataBooks example uses sections, but they\'re ge

相关标签:
7条回答
  • 2020-11-28 01:37

    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:

    • 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)


    // 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:

    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"];
    
    0 讨论(0)
  • 2020-11-28 01:38

    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:

    - (NSString *)stringGroupByFirstInitial {
        NSString *temp = [self uppercaseString];
        if (!temp.length || temp.length == 1)
            return self;
        return [temp substringToIndex:1];
    }
    
    0 讨论(0)
  • 2020-11-28 01:56

    swift 3

    first, create extension to NSString (because CoreData is using basically 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

    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

    let controller = NSFetchedResultsController(fetchRequest: request, managedObjectContext: context, sectionNameKeyPath: "lastname.firstChar", cacheName: "your_cache_name")
    
    0 讨论(0)
  • 2020-11-28 02:00

    I think I've got yet another option, this one uses a category on 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:

    - (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

    0 讨论(0)
  • 2020-11-28 02:00

    Here's how you might get 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.

    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.

    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. =)

    0 讨论(0)
  • 2020-11-28 02:00

    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.

    0 讨论(0)
提交回复
热议问题