From 4456f41a33e619c4b8f818179e42037661760b6a Mon Sep 17 00:00:00 2001 From: Guillermo Gonzalez Date: Fri, 18 Apr 2014 22:09:10 +0200 Subject: [PATCH] Add TGRFetchedResultsTableViewController --- TGRDataSource.xcodeproj/project.pbxproj | 6 + .../TGRFetchedResultsTableViewController.h | 49 +++++ .../TGRFetchedResultsTableViewController.m | 190 ++++++++++++++++++ 3 files changed, 245 insertions(+) create mode 100644 TGRDataSource/TGRFetchedResultsTableViewController.h create mode 100644 TGRDataSource/TGRFetchedResultsTableViewController.m diff --git a/TGRDataSource.xcodeproj/project.pbxproj b/TGRDataSource.xcodeproj/project.pbxproj index e731a02..28d5bdb 100644 --- a/TGRDataSource.xcodeproj/project.pbxproj +++ b/TGRDataSource.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 990C568A19017DAD00E382F1 /* NSObject+Abstract.m in Sources */ = {isa = PBXBuildFile; fileRef = 990C568919017DAD00E382F1 /* NSObject+Abstract.m */; }; 990C568D19017EBA00E382F1 /* TGRArrayDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 990C568C19017EBA00E382F1 /* TGRArrayDataSource.m */; }; 993DB7E71901990000653166 /* TGRFetchedResultsDataSource.m in Sources */ = {isa = PBXBuildFile; fileRef = 993DB7E61901990000653166 /* TGRFetchedResultsDataSource.m */; }; + 993DB7EA1901B86300653166 /* TGRFetchedResultsTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 993DB7E91901B86300653166 /* TGRFetchedResultsTableViewController.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -60,6 +61,8 @@ 990C568C19017EBA00E382F1 /* TGRArrayDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGRArrayDataSource.m; sourceTree = ""; }; 993DB7E51901990000653166 /* TGRFetchedResultsDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGRFetchedResultsDataSource.h; sourceTree = ""; }; 993DB7E61901990000653166 /* TGRFetchedResultsDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGRFetchedResultsDataSource.m; sourceTree = ""; }; + 993DB7E81901B86300653166 /* TGRFetchedResultsTableViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TGRFetchedResultsTableViewController.h; sourceTree = ""; }; + 993DB7E91901B86300653166 /* TGRFetchedResultsTableViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = TGRFetchedResultsTableViewController.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -126,6 +129,8 @@ 990C568C19017EBA00E382F1 /* TGRArrayDataSource.m */, 993DB7E51901990000653166 /* TGRFetchedResultsDataSource.h */, 993DB7E61901990000653166 /* TGRFetchedResultsDataSource.m */, + 993DB7E81901B86300653166 /* TGRFetchedResultsTableViewController.h */, + 993DB7E91901B86300653166 /* TGRFetchedResultsTableViewController.m */, ); path = TGRDataSource; sourceTree = ""; @@ -239,6 +244,7 @@ buildActionMask = 2147483647; files = ( 990C568719017CB400E382F1 /* TGRDataSource.m in Sources */, + 993DB7EA1901B86300653166 /* TGRFetchedResultsTableViewController.m in Sources */, 993DB7E71901990000653166 /* TGRFetchedResultsDataSource.m in Sources */, 990C568A19017DAD00E382F1 /* NSObject+Abstract.m in Sources */, 990C568D19017EBA00E382F1 /* TGRArrayDataSource.m in Sources */, diff --git a/TGRDataSource/TGRFetchedResultsTableViewController.h b/TGRDataSource/TGRFetchedResultsTableViewController.h new file mode 100644 index 0000000..43eccc9 --- /dev/null +++ b/TGRDataSource/TGRFetchedResultsTableViewController.h @@ -0,0 +1,49 @@ +// TGRFetchedResultsTableViewController.h +// +// Copyright (c) 2014 Guillermo Gonzalez +// +// 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 +#import + +@class TGRFetchedResultsDataSource; + +/** + `UITableViewController` subclass that takes its data from a `TGRFetchedResultsDataSource` object. + */ +@interface TGRFetchedResultsTableViewController : UITableViewController + +/** + The data source used by this view controller. + */ +@property (strong, nonatomic) TGRFetchedResultsDataSource *dataSource; + +/** + Maximum number of changes in the fetched results controller that will be animated. Default is 100. + */ +@property (nonatomic) NSUInteger contentAnimationMaximumChangeCount; + +/** + Performs the fetch and reloads the table view. + This method is called everytime the `dataSource` property changes. + */ +- (void)performFetch; + +@end diff --git a/TGRDataSource/TGRFetchedResultsTableViewController.m b/TGRDataSource/TGRFetchedResultsTableViewController.m new file mode 100644 index 0000000..dd6ec01 --- /dev/null +++ b/TGRDataSource/TGRFetchedResultsTableViewController.m @@ -0,0 +1,190 @@ +// TGRFetchedResultsTableViewController.m +// +// Copyright (c) 2014 Guillermo Gonzalez +// +// 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 "TGRFetchedResultsTableViewController.h" +#import "TGRFetchedResultsDataSource.h" + +@interface TGRFetchedResultsTableViewController () + +@property (strong, nonatomic) NSMutableIndexSet *insertedSections; +@property (strong, nonatomic) NSMutableIndexSet *deletedSections; +@property (strong, nonatomic) NSMutableArray *insertedRows; +@property (strong, nonatomic) NSMutableArray *deletedRows; +@property (strong, nonatomic) NSMutableArray *updatedRows; + +@end + +@implementation TGRFetchedResultsTableViewController + +#pragma mark - Properties + +- (void)setDataSource:(TGRFetchedResultsDataSource *)dataSource { + if (_dataSource != dataSource) { + _dataSource.fetchedResultsController.delegate = nil; + _dataSource = dataSource; + _dataSource.fetchedResultsController.delegate = self; + + self.tableView.dataSource = dataSource; + [self performFetch]; + } +} + +#pragma mark - Lifecycle + +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]; + + if (self) { + _contentAnimationMaximumChangeCount = 100; + } + + return self; +} + +- (void)performFetch { + if (self.dataSource) { + NSError *error = nil; + [self.dataSource.fetchedResultsController performFetch:&error]; + NSAssert(error == nil, @"%@ performFetch: %@", self, error); + } + + [self.tableView reloadData]; +} + +#pragma mark - NSFetchedResultsControllerDelegate + +- (void) controller:(NSFetchedResultsController *)controller + didChangeSection:(id )sectionInfo + atIndex:(NSUInteger)sectionIndex + forChangeType:(NSFetchedResultsChangeType)type { + switch (type) { + case NSFetchedResultsChangeInsert: + [self.insertedSections addIndex:sectionIndex]; + break; + + case NSFetchedResultsChangeDelete: + [self.deletedSections addIndex:sectionIndex]; + break; + + default: + break; + } +} + +- (void) controller:(NSFetchedResultsController *)controller + didChangeObject:(id)anObject + atIndexPath:(NSIndexPath *)indexPath + forChangeType:(NSFetchedResultsChangeType)type + newIndexPath:(NSIndexPath *)newIndexPath { + switch (type) { + case NSFetchedResultsChangeInsert: + if (![self.insertedSections containsIndex:(NSUInteger)newIndexPath.section]) { + [self.insertedRows addObject:newIndexPath]; + } + break; + + case NSFetchedResultsChangeDelete: + if (![self.deletedSections containsIndex:(NSUInteger)indexPath.section]) { + [self.deletedRows addObject:indexPath]; + } + break; + + case NSFetchedResultsChangeUpdate: + [self.updatedRows addObject:indexPath]; + break; + + case NSFetchedResultsChangeMove: + if (![self.insertedSections containsIndex:(NSUInteger)newIndexPath.section]) { + [self.insertedRows addObject:newIndexPath]; + } + if (![self.deletedSections containsIndex:(NSUInteger)indexPath.section]) { + [self.deletedRows addObject:indexPath]; + } + break; + } +} + +- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { + NSUInteger changeCount = self.insertedSections.count + + self.deletedSections.count + + self.insertedRows.count + + self.deletedRows.count + + self.updatedRows.count; + + if (changeCount <= self.contentAnimationMaximumChangeCount) { + [self.tableView beginUpdates]; + [self.tableView deleteSections:self.deletedSections withRowAnimation:UITableViewRowAnimationAutomatic]; + [self.tableView insertSections:self.insertedSections withRowAnimation:UITableViewRowAnimationAutomatic]; + [self.tableView deleteRowsAtIndexPaths:self.deletedRows withRowAnimation:UITableViewRowAnimationLeft]; + [self.tableView insertRowsAtIndexPaths:self.insertedRows withRowAnimation:UITableViewRowAnimationRight]; + [self.tableView reloadRowsAtIndexPaths:self.updatedRows withRowAnimation:UITableViewRowAnimationAutomatic]; + [self.tableView endUpdates]; + } + else { + [self.tableView reloadData]; + } + + self.insertedSections = nil; + self.deletedSections = nil; + self.insertedRows = nil; + self.deletedRows = nil; + self.updatedRows = nil; +} + +#pragma mark - Private + +- (NSMutableIndexSet *)insertedSections { + if (!_insertedSections) { + _insertedSections = [[NSMutableIndexSet alloc] init]; + } + return _insertedSections; +} + +- (NSMutableIndexSet *)deletedSections { + if (!_deletedSections) { + _deletedSections = [[NSMutableIndexSet alloc] init]; + } + return _deletedSections; +} + +- (NSMutableArray *)insertedRows { + if (!_insertedRows) { + _insertedRows = [[NSMutableArray alloc] init]; + } + return _insertedRows; +} + +- (NSMutableArray *)deletedRows { + if (!_deletedRows) { + _deletedRows = [[NSMutableArray alloc] init]; + } + return _deletedRows; +} + +- (NSMutableArray *)updatedRows { + if (!_updatedRows) { + _updatedRows = [[NSMutableArray alloc] init]; + } + return _updatedRows; +} + +@end