11

I have an NSFetchedResultsController which fetches data from a Core Data structure, a list of albums. It's currently sorted by the artist, so all the A's, B's, etc. I want to add an index so the user can quickly jump to each letter, and I'm using the code below to do it. The issue is that the section headers are now "A", "B", "C", etc., too, meaning I've lost the section headers in alphabetical order with each individual artist on it ("Adele", "America", "Beatles, The", etc.)

I'd like the index to use the letters A through Z, but the section headers to display the artist names in alphabetical order. Pushing a letter in the index would then jump to the first artist with that letter. How can I achieve this (alphabet characters in the index, but a different attribute for the section titles)?

EDIT: If I set my sectionNameKeyPath to my author field, the A-Z index is visible, but the letters aren't synced up to the names, for example if I tap S, it takes me to artists beginning with AL, or tapping G takes me to AB. I'm not quite sure why this is, perhaps the section index is screwing up?

Here's the code I'm using:

In my NSManagedObject subclass:

- (NSString *) firstLetter
{
    [self willAccessValueForKey: @"firstLetter"];
    NSString *firstLetter = [[[self author] substringToIndex: 1] uppercaseString];
    [self didAccessValueForKey: @"firstLetter"];
    return firstLetter;
}

In my View Controller, to create the FRC:

NSFetchedResultsController *byAuthorFRC = [[NSFetchedResultsController alloc] initWithFetchRequest: _fetchRequest
                                                                              managedObjectContext: [[CDManager sharedManager] managedObjectContext]
                                                                                sectionNameKeyPath: @"firstLetter"
                                                                                         cacheName: nil];

And here's my sectionIndexTitlesForTableView: method:

- (NSArray *) sectionIndexTitlesForTableView: (UITableView *) tableView
{
    return [self.fetchedResultsController sectionIndexTitles];
}
Luke
  • 9,512
  • 15
  • 82
  • 146
  • What happens if you use `sectionNameKeyPath:@"author"` ? – Martin R Apr 11 '13 at 09:01
  • @MartinR There is an A-Z index, but the letters aren't synced up to the names, for example if I tap S, it takes me to artists beginning with AL, or tapping G takes me to AB. – Luke Apr 11 '13 at 09:05
  • try this [link](http://stackoverflow.com/questions/15883892/custom-core-data-sectionnamekeypath/15885824#15885824) – Dan Shelly Apr 11 '13 at 09:40
  • @DanShelly Not sure I follow.. there's no mention of indexing in that link. What are you suggesting I do? – Luke Apr 11 '13 at 10:52
  • try to implement: `- (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section` – Dan Shelly Apr 11 '13 at 10:54
  • @DanShelly I already have that implemented – it worked fine before I added the index. The single line of code is: `return [[[self.fetchedResultsController sections] objectAtIndex: section] name];` – Luke Apr 11 '13 at 10:57
  • @DanShelly I'd also point out that if I use `firstLetter` as the `sectionNameKeyPath`, there are only 26 section headers anyway, one per letter. There should be hundreds. – Luke Apr 11 '13 at 10:59
  • @lukech: I cannot reproduce the problem. In my test app it works with `sectionNameKeyPath:@"author"`. Did you implement `tableView:sectionForSectionIndexTitle:atIndex:`? – Martin R Apr 11 '13 at 17:54
  • @MartinR No, I've not seen / used that method before. Would you be able to provide a code example for what I'd need to do in there? – Luke Apr 11 '13 at 18:46
  • @lukech: I am not sure if it is really necessary (my test app worked also without). Here http://developer.apple.com/library/ios/#documentation/CoreData/Reference/NSFetchedResultsController_Class/Reference/Reference.html you can find the default implementation. You can try that, perhaps it helps. – Martin R Apr 11 '13 at 18:58
  • @MartinR I've attempted to implement that method and it doesn't appear to have made a difference, so if there's any way you could let me see the code that has enabled you to make this work, I'd be very grateful. – Luke Apr 11 '13 at 21:39
  • @MartinR Oh, scratch that, I had it pointed at the wrong FRC, my mistake. If you want to write an answer, I'll happily mark it. – Luke Apr 11 '13 at 21:41

2 Answers2

20

sectionNameKeyPath:@"author" is the correct parameter, because that is how you want your table grouped into sections. The standard implementation

- (NSArray *) sectionIndexTitlesForTableView: (UITableView *) tableView
{
    return [self.fetchedResultsController sectionIndexTitles];
}

returns the first letter of each section header, so there is no need for a separate firstLetter method.

For the correct synchronization from the section index to the sections you have to implement the table view data source method tableView:sectionForSectionIndexTitle:atIndex:. The standard implementation in connection with a fetched results controller is:

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

Edit:

Swift:

func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
    return fetchedResultsController.sectionIndexTitles
}

func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
    return fetchedResultsController.sectionForSectionIndexTitle(title, atIndex: index)
} 
Adam Waite
  • 19,175
  • 22
  • 126
  • 148
Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
6

Swift 2

Add this var to your NSManagedObject

class Adherent: NSManagedObject {

    var initialNom: String {
        self.willAccessValueForKey("initialNom")
        let initial = (self.nom as NSString).substringToIndex(1)
        self.didAccessValueForKey("initialNom")
        return initial
    }

}

Then use it in your NSFetchedResultsController constructor (don't forget to sort with the "real" attribute)

let nomSort = NSSortDescriptor(key: "nom", ascending: true)
request.sortDescriptors = [nomSort]

self.fetchedResultController = NSFetchedResultsController(
    fetchRequest: request, 
    managedObjectContext: managedObjectContext, 
    sectionNameKeyPath: "initialNom", 
    cacheName: nil)

In your table view, implement (at least) these :

func numberOfSectionsInTableView(tableView: UITableView) -> Int {
    return self.fetchedResultController.sections!.count
}

func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    let sections = self.fetchedResultController.sections!
    let sectionInfo = sections[section]
    return sectionInfo.numberOfObjects
}

func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
    [...]
}

func sectionIndexTitlesForTableView(tableView: UITableView) -> [String]? {
    return self.fetchedResultController.sectionIndexTitles
}

func tableView(tableView: UITableView, sectionForSectionIndexTitle title: String, atIndex index: Int) -> Int {
    return self.fetchedResultController.sectionForSectionIndexTitle(title, atIndex: index)
}

func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
    return self.fetchedResultController.sectionIndexTitles[section]
}
Alexandre G.
  • 1,781
  • 14
  • 17