Search code examples
swiftuitableviewcore-dataswift4nsfetchedresultscontroller

How can I set NSFetchedResultsController's section sectionNameKeyPath to be the first letter of a attribute not just the attribute in Swift, NOT ObjC


I'm rewriting an old obj-c project in swift that has a tableView with a sectionIndex

I've set a predicate so it only returns objects with the same country attribute

I want to make the section index based on the first letter of the country attribute

in obj-c i created the fetchedResultsController like this

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

and i had an extension on NSString

  @implementation NSString (Indexing)

- (NSString *)stringGroupByFirstInitial {

    if (!self.length || self.length == 1)
        return self;

    return [self substringToIndex:1];
}

this worked fine in conjunction with the two methods

- (NSArray *) sectionIndexTitlesForTableView: (UITableView *) tableView{

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

    return [self.fetchedResultsController sectionForSectionIndexTitle:title atIndex:index];
}

In swift I tried to create a similar extension, with snappier name :)

extension String {
    func firstCharacter()-> String {
        let startIndex = self.startIndex
        let first =  self[...startIndex]
        return String(first)
    }
}

which works fine in a playground returning the string of the first character of any string you call it on.

but using a similar approach creating the fetchedResultsController, in swift, ...

let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                                          managedObjectContext: (dataModel?.container.viewContext)!,
                                                          sectionNameKeyPath: "country.firstCharacter()",
                                                          cacheName: nil)

...causes an exception. heres the console log

2018-02-23 11:41:20.762650-0800 Splash[5287:459654] *** Terminating app due to uncaught exception 'NSUnknownKeyException', reason: '[ valueForUndefinedKey:]: this class is not key value coding-compliant for the key firstCharacter().'

Any suggestions as the correct way to approach this would be appreciated

This is not a duplicate of the question suggested as this is specifically related to how to achieve this in Swift. I have added the simple correct way to achieve this in Obj -c to the other question


Solution

  • Add a function to your DiveSite class to return the first letter of the country:

    @objc func countryFirstCharacter() -> String {
        let startIndex = country.startIndex
        let first = country[...startIndex]
        return String(first)
    }
    

    Then use that function name (without the ()) as the sectionNameKeyPath:

    let fetchedResultsController = NSFetchedResultsController(fetchRequest: fetchRequest,
                                      managedObjectContext: (dataModel?.container.viewContext)!,
                                      sectionNameKeyPath: "countryFirstCharacter",
                                      cacheName: nil)
    

    Note that the @objc is necessary here in order to make the (Swift) function visible to the (Objective-C) FRC. (Sadly you can't just add @objc to your extension of String.)