Skip to content

Instantly share code, notes, and snippets.

@mbbischoff
Last active August 29, 2015 14:10
Show Gist options
  • Save mbbischoff/5cba982b72c2723eb1e7 to your computer and use it in GitHub Desktop.
Save mbbischoff/5cba982b72c2723eb1e7 to your computer and use it in GitHub Desktop.
LCKIndexedFetchedResultsController is a NSFetchedResultsController subclass that attempts to solve the problem of sorting a fetched collection into correctly alphabetized sections for every locale. It does things like making sure that the `#` character shows up at the bottom instead of the top of the section index titles.
//
// LCKIndexedFetchedResultsController.h
// Quotebook
//
// Created by Andrew Harrison on 7/26/14.
// Copyright (c) 2014 Lickability. All rights reserved.
//
@import CoreData;
/// An NSFetchedResultsController that supports proper localized indexes.
@interface LCKIndexedFetchedResultsController : NSFetchedResultsController
@property (nonatomic, weak) NSObject <NSFetchedResultsControllerDelegate> *delegate;
@end
//
// LCKIndexedFetchedResultsController.m
// Quotebook
//
// Created by Andrew Harrison on 7/26/14.
// Copyright (c) 2014 Lickability. All rights reserved.
//
#import "LCKIndexedFetchedResultsController.h"
static const char LCKIndexedFetchedResultsControllerPoundSymbol = '#';
@interface LCKIndexedFetchedResultsController () <NSFetchedResultsControllerDelegate>
/// An internal fetched results controller that this class the delegate of.
@property (nonatomic) NSFetchedResultsController *fetchedResultsController;
/// A comparator used to sort the sections
@property (nonatomic, readonly) NSComparator sectionsComparator;
@property (nonatomic) NSArray *sectionsBeforeChange;
@end
@implementation LCKIndexedFetchedResultsController
#pragma mark - NSObject
- (void)dealloc {
_fetchedResultsController.delegate = nil;
}
#pragma mark - NSFetchedResultsController
- (instancetype)initWithFetchRequest:(NSFetchRequest *)fetchRequest managedObjectContext:(NSManagedObjectContext *)context sectionNameKeyPath:(NSString *)sectionNameKeyPath cacheName:(NSString *)name {
self = [super init]; // Skip calling the desigated initalizer because we won’t use any of it’s functionality.
if (self) {
_fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:context sectionNameKeyPath:sectionNameKeyPath cacheName:name];
_fetchedResultsController.delegate = self;
}
return self;
}
- (NSManagedObjectContext *)managedObjectContext {
return self.fetchedResultsController.managedObjectContext;
}
- (NSFetchRequest *)fetchRequest {
return self.fetchedResultsController.fetchRequest;
}
- (NSString *)sectionNameKeyPath {
return self.fetchedResultsController.sectionNameKeyPath;
}
- (NSString *)cacheName {
return self.fetchedResultsController.cacheName;
}
- (BOOL)performFetch:(NSError *__autoreleasing *)error {
return [self.fetchedResultsController performFetch:error];
}
- (NSArray *)fetchedObjects {
return self.fetchedResultsController.fetchedObjects;
}
#pragma mark - Modified
- (id)objectAtIndexPath:(NSIndexPath *)indexPath {
id <NSFetchedResultsSectionInfo> sectionInfo = [self.sections objectAtIndex:indexPath.section];
return [sectionInfo.objects objectAtIndex:indexPath.row];
}
- (NSIndexPath *)indexPathForObject:(id)object {
__block NSIndexPath *indexPath;
[self.sections enumerateObjectsUsingBlock:^(id <NSFetchedResultsSectionInfo> sectionInfo, NSUInteger sectionIndex, BOOL *stop) {
[[sectionInfo objects] enumerateObjectsUsingBlock:^(id rowObject, NSUInteger rowIndex, BOOL *innerStop) {
if (object == rowObject) {
indexPath = [NSIndexPath indexPathForRow:rowIndex inSection:sectionIndex];
*innerStop = YES;
*stop = YES;
}
}];
}];
return indexPath;
}
- (NSInteger)sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)sectionIndex {
return [[UILocalizedIndexedCollation currentCollation] sectionForSectionIndexTitleAtIndex:sectionIndex];
}
- (NSArray *)sectionIndexTitles {
return [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles];
}
- (NSArray *)sections {
return [self.fetchedResultsController.sections sortedArrayUsingComparator:self.sectionsComparator];
}
#pragma mark - LCKFacadeResultsController
- (NSComparator)sectionsComparator {
return ^NSComparisonResult(id <NSFetchedResultsSectionInfo> sectionInfo1, id <NSFetchedResultsSectionInfo> sectionInfo2) {
char obj1FirstChar = [[sectionInfo1 name] characterAtIndex:0];
char obj2FirstChar = [[sectionInfo2 name] characterAtIndex:0];
if (obj1FirstChar == LCKIndexedFetchedResultsControllerPoundSymbol && obj2FirstChar != LCKIndexedFetchedResultsControllerPoundSymbol) {
return NSOrderedDescending;
}
else if (obj2FirstChar == LCKIndexedFetchedResultsControllerPoundSymbol && obj1FirstChar != LCKIndexedFetchedResultsControllerPoundSymbol) {
return NSOrderedAscending;
}
else {
return [[sectionInfo1 name] compare:[sectionInfo2 name] options:0];
}
};
}
- (NSInteger)sortedSectionIndexForUnsortedIndex:(NSInteger)unsortedIndex {
NSArray *unsortedSections = self.fetchedResultsController.sections;
NSArray *sortedSections = self.sections;
id <NSFetchedResultsSectionInfo> sectionInfo = [unsortedSections safeObjectAtIndex:unsortedIndex];
return [sortedSections indexOfObject:sectionInfo];
}
#pragma mark - NSFetchedResultsControllerDelegate
- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
if (indexPath) {
// Construct an updated old index path based on the section’s new index after sorting.
NSInteger sortedSectionIndex = [self sortedSectionIndexForUnsortedIndex:indexPath.section];
indexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:sortedSectionIndex];
}
if (newIndexPath) {
newIndexPath = [self indexPathForObject:anObject];
}
if ([self.delegate respondsToSelector:_cmd] && indexPath.section != NSNotFound) {
[self.delegate controller:self didChangeObject:anObject atIndexPath:indexPath forChangeType:type newIndexPath:newIndexPath];
}
}
- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
if (type == NSFetchedResultsChangeDelete) {
sectionIndex = [self.sectionsBeforeChange indexOfObject:sectionInfo];
}
else {
sectionIndex = [self.sections indexOfObject:sectionInfo];
}
if ([self.delegate respondsToSelector:_cmd]) {
[self.delegate controller:self didChangeSection:sectionInfo atIndex:sectionIndex forChangeType:type];
}
}
- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
self.sectionsBeforeChange = self.sections;
if ([self.delegate respondsToSelector:_cmd]) {
[self.delegate controllerWillChangeContent:self];
}
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
if ([self.delegate respondsToSelector:_cmd]) {
[self.delegate controllerDidChangeContent:self];
}
}
- (NSString *)controller:(NSFetchedResultsController *)controller sectionIndexTitleForSectionName:(NSString *)sectionName {
if ([self.delegate respondsToSelector:_cmd]) {
return [self.delegate controller:self sectionIndexTitleForSectionName:sectionName];
}
return nil;
}
@end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment