blob: cecfe3b9ba57d014aaf63d5d7db1345b3e68c3af [file] [log] [blame]
//
// SMCollectionViewDataSource.m
// StoreMad
//
// Created by Andrew Smith on 2/23/13.
// Copyright (c) 2012 Andrew B. Smith ( http://github.com/drewsmits ). All rights reserved.
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is furnished to do so,
// subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included
// in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#import "SMCollectionViewDataSource.h"
@interface SMCollectionViewDataSource ()
@property (nonatomic, strong) NSMutableArray *objectChanges;
@property (nonatomic, strong) NSMutableArray *sectionChanges;
@property (nonatomic, weak) UICollectionView *collectionView;
@end
@implementation SMCollectionViewDataSource
- (id)init
{
self = [super init];
if (self) {
_objectChanges = [NSMutableArray array];
_sectionChanges = [NSMutableArray array];
}
return self;
}
- (void)setupWithCollectionView:(UICollectionView *)collectionView
fetchRequest:(NSFetchRequest *)fetchRequest
context:(NSManagedObjectContext *)context
sectionNameKeyPath:(NSString *)sectionNameKeyPath
cacheName:(NSString *)cacheName
{
// Build controller
self.fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest
managedObjectContext:context
sectionNameKeyPath:sectionNameKeyPath
cacheName:cacheName];
// Respond to changes here
self.fetchedResultsController.delegate = self;
// Hold onto this to work with when changes happen
self.collectionView = collectionView;
}
#pragma mark - Change
- (void)controller:(NSFetchedResultsController *)controller
didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
atIndex:(NSUInteger)sectionIndex
forChangeType:(NSFetchedResultsChangeType)type
{
NSMutableDictionary *change = [NSMutableDictionary new];
switch(type) {
case NSFetchedResultsChangeInsert:
change[@(type)] = @(sectionIndex);
break;
case NSFetchedResultsChangeDelete:
change[@(type)] = @(sectionIndex);
break;
case NSFetchedResultsChangeUpdate:
change[@(type)] = @(sectionIndex);
break;
case NSFetchedResultsChangeMove:
// Not handled
break;
}
[self.sectionChanges addObject:change];
}
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
NSMutableDictionary *change = [NSMutableDictionary new];
switch(type) {
case NSFetchedResultsChangeInsert:
change[@(type)] = newIndexPath;
break;
case NSFetchedResultsChangeDelete:
change[@(type)] = indexPath;
break;
case NSFetchedResultsChangeUpdate:
change[@(type)] = indexPath;
break;
case NSFetchedResultsChangeMove:
change[@(type)] = @[indexPath, newIndexPath];
break;
}
[self.objectChanges addObject:change];
}
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
if ([self.sectionChanges count] > 0) {
[self.collectionView performBatchUpdates:^{
for (NSDictionary *change in self.sectionChanges) {
[change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id sectionIndex, BOOL *stop) {
NSFetchedResultsChangeType type = [key unsignedIntegerValue];
switch (type) {
case NSFetchedResultsChangeInsert:
[self.collectionView insertSections:[NSIndexSet indexSetWithIndex:[sectionIndex unsignedIntegerValue]]];
break;
case NSFetchedResultsChangeDelete:
[self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:[sectionIndex unsignedIntegerValue]]];
break;
case NSFetchedResultsChangeUpdate:
[self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:[sectionIndex unsignedIntegerValue]]];
break;
case NSFetchedResultsChangeMove:
[self.collectionView moveSection:[sectionIndex[0] unsignedIntegerValue]
toSection:[sectionIndex[1] unsignedIntegerValue]];
break;
}
}];
}
} completion:nil];
}
if ([self.objectChanges count] > 0 && [self.sectionChanges count] == 0) {
if ([self shouldReloadCollectionViewToPreventKnownIssue]) {
// This is to prevent a bug in UICollectionView from occurring.
// The bug presents itself when inserting the first object or deleting the last object in a collection view.
// http://stackoverflow.com/questions/12611292/uicollectionview-assertion-failure
// This code should be removed once the bug has been fixed, it is tracked in OpenRadar
// http://openradar.appspot.com/12954582
[self.collectionView reloadData];
} else {
[self.collectionView performBatchUpdates:^{
for (NSDictionary *change in _objectChanges) {
[change enumerateKeysAndObjectsUsingBlock:^(NSNumber *key, id indexPath, BOOL *stop) {
NSFetchedResultsChangeType type = [key unsignedIntegerValue];
switch (type) {
case NSFetchedResultsChangeInsert:
[self.collectionView insertItemsAtIndexPaths:@[indexPath]];
break;
case NSFetchedResultsChangeDelete:
[self.collectionView deleteItemsAtIndexPaths:@[indexPath]];
break;
case NSFetchedResultsChangeUpdate:
// If you call reload here, you can run into a Core Data crash,
// where you see [__NSArrayM insertObject:atIndex:]: object cannot be nil.
// To be honest, I'm not sure what this is, could be a bug.
// [self.collectionView :@[indexPath]];
// Instead, use reload here.
[self.dataSourceDelegate configureCell:[self.collectionView cellForItemAtIndexPath:indexPath]
atIndexPath:indexPath];
break;
case NSFetchedResultsChangeMove:
[self.collectionView moveItemAtIndexPath:indexPath[0] toIndexPath:indexPath[1]];
break;
}
}];
}
} completion:nil];
}
[self.sectionChanges removeAllObjects];
[self.objectChanges removeAllObjects];
}
}
- (BOOL)shouldReloadCollectionViewToPreventKnownIssue
{
__block BOOL shouldReload = NO;
for (NSDictionary *change in self.objectChanges) {
[change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) {
NSFetchedResultsChangeType type = [key unsignedIntegerValue];
NSIndexPath *indexPath = obj;
switch (type) {
case NSFetchedResultsChangeInsert:
if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) {
shouldReload = YES;
} else {
shouldReload = NO;
}
break;
case NSFetchedResultsChangeDelete:
if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) {
shouldReload = YES;
} else {
shouldReload = NO;
}
break;
case NSFetchedResultsChangeUpdate:
shouldReload = NO;
break;
case NSFetchedResultsChangeMove:
shouldReload = NO;
break;
}
}];
}
return shouldReload;
}
@end