| // |
| // 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 |