Last active
August 29, 2015 14:10
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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