diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h index 226e05c..96fc874 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h @@ -10,7 +10,8 @@ @import CoreData; @import UIKit; -@interface SQKFetchedCollectionViewController : UIViewController +IB_DESIGNABLE +@interface SQKFetchedCollectionViewController : UIViewController /** * Initialises a Core Data-backed UICollectionViewController with a search bar. @@ -36,8 +37,12 @@ /** * The collection view shown by the view controller. */ -@property (strong, nonatomic, readonly) UICollectionView *collectionView; -@property (strong, nonatomic, readonly) UICollectionViewLayout *collectionViewLayout; +@property (strong, nonatomic) IBOutlet UICollectionView *collectionView; + +/** + * The collection view layout used by the view controller. + */ +@property (strong, nonatomic) UICollectionViewLayout *layout; /** * An optional refresh control shown when pulling down the collectionview. @@ -52,7 +57,7 @@ * executed fetch requests take longer when sections are used. When searching this is * especially noticable as a new fetch request is executed upon each key stroke during search. */ -@property (nonatomic, assign) BOOL showsSectionsWhenSearching; +@property (nonatomic, assign) IBInspectable BOOL showsSectionsWhenSearching; /** * The managed object context backing the fetched results controller. @@ -68,7 +73,7 @@ * BOOL that when set to YES sets a search bar is added to the top of the collection view. * Default is set to YES */ -@property (nonatomic, assign) BOOL searchingEnabled; +@property (nonatomic, assign) IBInspectable BOOL searchingEnabled; /** * Exposing the search bar so it can be customised. diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m index 8677da9..b743fbf 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m @@ -23,26 +23,10 @@ @interface SQKFetchedCollectionViewController () @property (strong, nonatomic) NSMutableArray *sectionChanges; @property (strong, nonatomic) NSMutableArray *itemChanges; -@property (strong, nonatomic, readwrite) UICollectionView *collectionView; -@property (strong, nonatomic, readwrite) UICollectionViewLayout *collectionViewLayout; - @end @implementation SQKFetchedCollectionViewController -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context -{ - self = [super init]; - if (self) - { - self.managedObjectContext = context; - self.searchingEnabled = YES; - self.collectionViewLayout = layout; - } - - return self; -} - - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context searchingEnabled:(BOOL)searchingEnabled { self = [super init]; @@ -51,30 +35,33 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout co { self.managedObjectContext = context; self.searchingEnabled = searchingEnabled; - self.collectionViewLayout = layout; + self.layout = layout; } return self; } -- (instancetype)initWithCoder:(NSCoder *)coder +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context { - self = [super initWithCoder:coder]; - - if (self) - { - self.searchingEnabled = YES; - } - return self; + return [self initWithCollectionViewLayout:layout context:context searchingEnabled:YES]; } -- (void)loadView +- (void)viewDidLoad { - self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.collectionViewLayout]; - self.collectionView.autoresizingMask = UIViewAutoresizingFlexibleHeight | UIViewAutoresizingFlexibleWidth; - self.collectionView.delegate = self; - self.collectionView.dataSource = self; - self.view = self.collectionView; + [super viewDidLoad]; + + if (!self.collectionView) + { + self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.layout]; + self.collectionView.delegate = self; + self.collectionView.dataSource = self; + self.collectionView.collectionViewLayout = self.layout; + self.view = self.collectionView; + } + else + { + self.layout = self.collectionView.collectionViewLayout; + } if (self.searchingEnabled) { @@ -86,11 +73,6 @@ - (void)loadView self.searchBar.delegate = self; [self.collectionView addSubview:self.searchBar]; } -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; self.fetchedResultsController = [self fetchedResultsControllerWithSearch:nil]; @@ -176,51 +158,51 @@ - (BOOL)shouldReloadCollectionViewToPreventKnownIssue { __block BOOL shouldReload = NO; [self.itemChanges enumerateObjectsUsingBlock:^(NSDictionary *change, NSUInteger idx, BOOL *stop) { - [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - NSFetchedResultsChangeType type = [key unsignedIntegerValue]; - NSIndexPath *indexPath = obj; - - switch (type) + [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + NSFetchedResultsChangeType type = [key unsignedIntegerValue]; + NSIndexPath *indexPath = obj; + + switch (type) + { + case NSFetchedResultsChangeInsert: { - case NSFetchedResultsChangeInsert: + if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) { - if ([self.collectionView numberOfItemsInSection:indexPath.section] == 0) - { - shouldReload = YES; - } - else - { - shouldReload = NO; - } - break; + shouldReload = YES; } - - case NSFetchedResultsChangeDelete: + else { - if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) - { - shouldReload = YES; - } - else - { - shouldReload = NO; - } - break; + shouldReload = NO; } - - case NSFetchedResultsChangeUpdate: + break; + } + + case NSFetchedResultsChangeDelete: + { + if ([self.collectionView numberOfItemsInSection:indexPath.section] == 1) { - shouldReload = NO; - break; + shouldReload = YES; } - - case NSFetchedResultsChangeMove: + else { shouldReload = NO; - break; } + break; + } + + case NSFetchedResultsChangeUpdate: + { + shouldReload = NO; + break; + } + + case NSFetchedResultsChangeMove: + { + shouldReload = NO; + break; } - }]; + } + }]; }]; @@ -248,9 +230,9 @@ - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller } - (void)controller:(NSFetchedResultsController *)controller - didChangeSection:(id)sectionInfo - atIndex:(NSUInteger)sectionIndex - forChangeType:(NSFetchedResultsChangeType)type + didChangeSection:(id)sectionInfo + atIndex:(NSUInteger)sectionIndex + forChangeType:(NSFetchedResultsChangeType)type { NSMutableDictionary *change = [NSMutableDictionary dictionary]; change[@(type)] = @(sectionIndex); @@ -258,10 +240,10 @@ - (void)controller:(NSFetchedResultsController *)controller } - (void)controller:(NSFetchedResultsController *)controller - didChangeObject:(id)anObject - atIndexPath:(NSIndexPath *)indexPath - forChangeType:(NSFetchedResultsChangeType)type - newIndexPath:(NSIndexPath *)newIndexPath + didChangeObject:(id)anObject + atIndexPath:(NSIndexPath *)indexPath + forChangeType:(NSFetchedResultsChangeType)type + newIndexPath:(NSIndexPath *)newIndexPath { NSMutableDictionary *change = [[NSMutableDictionary alloc] init]; switch (type) @@ -303,82 +285,83 @@ - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller else { [self.collectionView performBatchUpdates:^{ - - //Deal with the sections - [self.sectionChanges enumerateObjectsUsingBlock:^(NSDictionary *change, NSUInteger idx, BOOL *stop) { - - [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - - NSFetchedResultsChangeType type = [key integerValue]; - - switch(type) - { - case NSFetchedResultsChangeInsert: - { - [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]]; - break; - } - case NSFetchedResultsChangeUpdate: - { - [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]]; - break; - } - case NSFetchedResultsChangeMove: - { - NSArray *moves = change[key]; - [moves enumerateObjectsUsingBlock:^(NSArray *move, NSUInteger idx, BOOL *stop) { - [self.collectionView moveSection:[move[0] integerValue] toSection:[move[1] integerValue]]; - }]; - break; - } - case NSFetchedResultsChangeDelete: - { - [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]]; - break; - } - } - }]; + + //Deal with the sections + [self.sectionChanges enumerateObjectsUsingBlock:^(NSDictionary *change, NSUInteger idx, BOOL *stop) { + + [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + + NSFetchedResultsChangeType type = [key integerValue]; + + switch (type) + { + case NSFetchedResultsChangeInsert: + { + [self.collectionView insertSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]]; + break; + } + case NSFetchedResultsChangeUpdate: + { + [self.collectionView reloadSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]]; + break; + } + case NSFetchedResultsChangeMove: + { + NSArray *moves = change[key]; + [moves enumerateObjectsUsingBlock:^(NSArray *move, NSUInteger idx, BOOL *stop) { + [self.collectionView moveSection:[move[0] integerValue] toSection:[move[1] integerValue]]; + }]; + break; + } + case NSFetchedResultsChangeDelete: + { + [self.collectionView deleteSections:[NSIndexSet indexSetWithIndex:[obj unsignedIntegerValue]]]; + break; + } + } }]; - - //Now deal with the items - [self.itemChanges enumerateObjectsUsingBlock:^(NSDictionary *change, NSUInteger idx, BOOL *stop) { - - [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { - - NSFetchedResultsChangeType type = [key unsignedIntegerValue]; - - switch(type) - { - case NSFetchedResultsChangeInsert: - { - [self.collectionView insertItemsAtIndexPaths:@[obj]]; - break; - } - case NSFetchedResultsChangeDelete: - { - [self.collectionView deleteItemsAtIndexPaths:@[obj]]; - break; - } - case NSFetchedResultsChangeUpdate: - { - [self fetchedResultsController:self.fetchedResultsController - configureItemCell:[self.collectionView cellForItemAtIndexPath:obj] - atIndexPath:obj]; - break; - } - case NSFetchedResultsChangeMove: - { - [self.collectionView moveItemAtIndexPath:obj[0] toIndexPath:obj[1]]; - break; - } - } - }]; + }]; + + //Now deal with the items + [self.itemChanges enumerateObjectsUsingBlock:^(NSDictionary *change, NSUInteger idx, BOOL *stop) { + + [change enumerateKeysAndObjectsUsingBlock:^(id key, id obj, BOOL *stop) { + + NSFetchedResultsChangeType type = [key unsignedIntegerValue]; + + switch (type) + { + case NSFetchedResultsChangeInsert: + { + [self.collectionView insertItemsAtIndexPaths:@[ obj ]]; + break; + } + case NSFetchedResultsChangeDelete: + { + [self.collectionView deleteItemsAtIndexPaths:@[ obj ]]; + break; + } + case NSFetchedResultsChangeUpdate: + { + [self fetchedResultsController:self.fetchedResultsController + configureItemCell:[self.collectionView cellForItemAtIndexPath:obj] + atIndexPath:obj]; + break; + } + case NSFetchedResultsChangeMove: + { + [self.collectionView moveItemAtIndexPath:obj[0] toIndexPath:obj[1]]; + break; + } + } }]; + }]; - } completion:^(BOOL finished) { - self.sectionChanges = nil; - self.itemChanges = nil; - }]; + } + completion:^(BOOL finished) { + self.sectionChanges = nil; + self.itemChanges = nil; + }]; } } @@ -441,7 +424,7 @@ - (BOOL)searchBarShouldBeginEditing:(UISearchBar *)searchBar */ self.collectionView.scrollEnabled = NO; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.8 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - self.collectionView.scrollEnabled = YES; + self.collectionView.scrollEnabled = YES; }); self.searchIsActive = YES; diff --git a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h index a49a224..1355c5f 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h @@ -14,6 +14,7 @@ * This class provides a simpler way to replicate the often-used pattern of a searchable Core * Data-backed table view. Must be used as a subclass. */ +IB_DESIGNABLE @interface SQKFetchedTableViewController : UITableViewController @@ -55,7 +56,7 @@ * BOOL that when set to YES sets the table view controller's header view to a search view controller. * Default is set to YES */ -@property (nonatomic, assign) BOOL searchingEnabled; +@property (nonatomic, assign) IBInspectable BOOL searchingEnabled; /** * Returns YES if the user is actively searching, i.e. the search bar has begun editing. Returns NO @@ -69,7 +70,7 @@ */ @property (strong, nonatomic, readonly) UISearchDisplayController *searchController; -@property (strong, nonatomic) UIView *emptyView; +@property (strong, nonatomic) IBOutlet UIView *emptyView; /** * Returns the currently active UITableView (i.e. regular or search). diff --git a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m index a5ae4d2..df6ac7c 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m @@ -163,93 +163,85 @@ - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller * Callbacks could be on a non-main thread. */ [self.managedObjectContext performBlockAndWait:^{ - UITableView *tableView = controller == self.fetchedResultsController ? - self.tableView : - self.searchController.searchResultsTableView; - [tableView beginUpdates]; + UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchController.searchResultsTableView; + [tableView beginUpdates]; }]; } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self.managedObjectContext performBlockAndWait:^{ - UITableView *tableView = controller == self.fetchedResultsController ? - self.tableView : - self.searchController.searchResultsTableView; - [tableView endUpdates]; + UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchController.searchResultsTableView; + [tableView endUpdates]; }]; } - (void)controller:(NSFetchedResultsController *)controller - didChangeSection:(id)sectionInfo - atIndex:(NSUInteger)sectionIndex - forChangeType:(NSFetchedResultsChangeType)type + didChangeSection:(id)sectionInfo + atIndex:(NSUInteger)sectionIndex + forChangeType:(NSFetchedResultsChangeType)type { [self.managedObjectContext performBlockAndWait:^{ - UITableView *tableView = controller == self.fetchedResultsController ? - self.tableView : - self.searchController.searchResultsTableView; - - switch (type) - { - case NSFetchedResultsChangeInsert: - [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] - withRowAnimation:UITableViewRowAnimationAutomatic]; - break; - - case NSFetchedResultsChangeDelete: - [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] - withRowAnimation:UITableViewRowAnimationAutomatic]; - break; - case NSFetchedResultsChangeMove: - // Not used for section changes - break; - case NSFetchedResultsChangeUpdate: - // Not used for section changes - break; - } + UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchController.searchResultsTableView; + + switch (type) + { + case NSFetchedResultsChangeInsert: + [tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] + withRowAnimation:UITableViewRowAnimationAutomatic]; + break; + + case NSFetchedResultsChangeDelete: + [tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] + withRowAnimation:UITableViewRowAnimationAutomatic]; + break; + case NSFetchedResultsChangeMove: + // Not used for section changes + break; + case NSFetchedResultsChangeUpdate: + // Not used for section changes + break; + } }]; } - (void)controller:(NSFetchedResultsController *)controller - didChangeObject:(id)anObject - atIndexPath:(NSIndexPath *)theIndexPath - forChangeType:(NSFetchedResultsChangeType)type - newIndexPath:(NSIndexPath *)newIndexPath + didChangeObject:(id)anObject + atIndexPath:(NSIndexPath *)theIndexPath + forChangeType:(NSFetchedResultsChangeType)type + newIndexPath:(NSIndexPath *)newIndexPath { [self.managedObjectContext performBlockAndWait:^{ - UITableView *tableView = controller == self.fetchedResultsController ? - self.tableView : - self.searchController.searchResultsTableView; - - [self showEmptyView:([[controller fetchedObjects] count] == 0)]; - - switch (type) - { - case NSFetchedResultsChangeInsert: - [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] - withRowAnimation:UITableViewRowAnimationFade]; - break; - - case NSFetchedResultsChangeDelete: - [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] - withRowAnimation:UITableViewRowAnimationFade]; - break; - case NSFetchedResultsChangeUpdate: - [self fetchedResultsController:controller - configureCell:[tableView cellForRowAtIndexPath:theIndexPath] - atIndexPath:theIndexPath]; - break; - - case NSFetchedResultsChangeMove: - [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] - withRowAnimation:UITableViewRowAnimationFade]; - [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] - withRowAnimation:UITableViewRowAnimationFade]; - break; - } + UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchController.searchResultsTableView; + + [self showEmptyView:([[[self activeFetchedResultsController] fetchedObjects] count] == 0)]; + + switch (type) + { + case NSFetchedResultsChangeInsert: + [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] + withRowAnimation:UITableViewRowAnimationFade]; + break; + + case NSFetchedResultsChangeDelete: + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] + withRowAnimation:UITableViewRowAnimationFade]; + break; + case NSFetchedResultsChangeUpdate: + [self fetchedResultsController:controller + configureCell:[tableView cellForRowAtIndexPath:theIndexPath] + atIndexPath:theIndexPath]; + break; + + case NSFetchedResultsChangeMove: + [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:theIndexPath] + withRowAnimation:UITableViewRowAnimationFade]; + [tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] + withRowAnimation:UITableViewRowAnimationFade]; + break; + } }]; } @@ -292,6 +284,8 @@ - (NSFetchedResultsController *)newFetchedResultsControllerWithSearch:(NSString abort(); } + [self showEmptyView:([fetchedResultsController.fetchedObjects count] == 0)]; + return fetchedResultsController; } @@ -411,14 +405,37 @@ - (void)showEmptyView:(BOOL)show { if (_emptyView) { + BOOL isSearching = (self.searchController && self.searchController.active); + UITableView *tableView = isSearching ? self.searchController.searchResultsTableView : self.tableView; + if (show) { - _emptyView.center = self.view.center; - [self.view addSubview:_emptyView]; + if (!isSearching) + { + self.tableView.tableHeaderView = nil; + } + + NSInteger emptyViewYPosition = 44 * 5 - (22); + _emptyView.center = CGPointMake(self.view.center.x, emptyViewYPosition); + + [tableView addSubview:_emptyView]; + + dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ + for (UIView *v in self.searchDisplayController.searchResultsTableView.subviews) + { + if ([v isKindOfClass:[UILabel class]] && + [[(UILabel *)v text] isEqualToString:@"No Results"]) + { + [(UILabel *)v setText:@""]; + break; + } + } + }); } else { - [self.emptyView removeFromSuperview]; + self.tableView.tableHeaderView = self.searchController.searchBar; + [_emptyView removeFromSuperview]; } } } diff --git a/Classes/shared/SQKCoreDataOperation/SQKCoreDataOperation.m b/Classes/shared/SQKCoreDataOperation/SQKCoreDataOperation.m index 506263d..3f57b93 100644 --- a/Classes/shared/SQKCoreDataOperation/SQKCoreDataOperation.m +++ b/Classes/shared/SQKCoreDataOperation/SQKCoreDataOperation.m @@ -6,9 +6,9 @@ // Copyright (c) 2013 3Squared. All rights reserved. // -#import "SQKCoreDataOperation.h" -#import "SQKContextManager.h" #import "NSManagedObjectContext+SQKAdditions.h" +#import "SQKContextManager.h" +#import "SQKCoreDataOperation.h" #import "SQKDataKitErrors.h" @interface SQKCoreDataOperation () @@ -64,7 +64,7 @@ - (void)start self.managedObjectContextToMerge = [self.contextManager newPrivateContext]; self.managedObjectContextToMerge.shouldMergeOnSave = NO; [self.managedObjectContextToMerge performBlockAndWait:^{ - [self performWorkWithPrivateContext:self.managedObjectContextToMerge]; + [self performWorkWithPrivateContext:self.managedObjectContextToMerge]; }]; } @@ -99,14 +99,14 @@ - (void)completeOperationBySavingContext:(NSManagedObjectContext *)managedObject name:NSManagedObjectContextDidSaveNotification object:nil]; - NSError *error = nil; - [managedObjectContext save:&error]; - if (error) - { - [self addError:error]; - [self finishOperation]; - } - } + NSError *error = nil; + [managedObjectContext save:&error]; + if (error) + { + [self addError:error]; + [self finishOperation]; + } + } } - (void)finishOperation @@ -154,67 +154,82 @@ - (void)addError:(NSError *)error - (void)contextSaveNotificationReceived:(NSNotification *)notification { - /** - * Usually core data operartions do not happen on the main thread. - * But as we are saving into the main context that must happen on the main thread. - * - * This is done using GCD to ensure the block is performed on the main thread. - * The issue is that once the private context has been merged into the main context, the core data - * operation needs to call `finish` on the context for the operation. - * - * To solve this semaphores can be used to halt the method until a notification is posted - * to the semaphore. - */ - dispatch_semaphore_t mainContextSavedSemaphore = dispatch_semaphore_create(0); - - //Ensure mainContext is accessed on the main thread. - dispatch_async(dispatch_get_main_queue(), ^{ - - NSManagedObjectContext *mainContext = self.contextManager.mainContext; - [mainContext performBlock:^{ - - NSManagedObjectContext *managedObjectContext = [notification object]; - - /** - * If NSManagedObjectContext from the notitification is a private context - * then merge the changes into the main context. - */ - if (managedObjectContext == self.managedObjectContextToMerge) - { - [mainContext mergeChangesFromContextDidSaveNotification:notification]; - - /** - * This loop is needed for 'correct' behaviour of NSFetchedResultsControllers. - * - * NSManagedObjectContext doesn't event fire - * NSManagedObjectContextObjectsDidChangeNotification for updated objects on merge, - * only inserted. - * - * SEE: - * http://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different - * May also have memory implications. - */ - for (NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) - { - [[mainContext objectWithID:[object objectID]] willAccessValueForKey:nil]; - } - - [[NSNotificationCenter defaultCenter] removeObserver:self - name:NSManagedObjectContextDidSaveNotification - object:nil]; - - dispatch_semaphore_signal(mainContextSavedSemaphore); - } - }]; - }); - - dispatch_semaphore_wait(mainContextSavedSemaphore, DISPATCH_TIME_FOREVER); - - /** + /** + * Usually core data operartions do not happen on the main thread. + * But as we are saving into the main context that must happen on the main thread. + * + * This is done using GCD to ensure the block is performed on the main thread. + * The issue is that once the private context has been merged into the main context, the core data + * operation needs to call `finish` on the context for the operation. + * + * To solve this semaphores can be used to halt the method until a notification is posted + * to the semaphore. + */ + dispatch_semaphore_t mainContextSavedSemaphore = dispatch_semaphore_create(0); + + void (^mergeBlock)() = ^{ + + NSManagedObjectContext *mainContext = self.contextManager.mainContext; + [mainContext performBlock:^{ + + NSManagedObjectContext *managedObjectContext = [notification object]; + + /** + * If NSManagedObjectContext from the notitification is a private context + * then merge the changes into the main context. + */ + if (managedObjectContext == self.managedObjectContextToMerge) + { + [mainContext mergeChangesFromContextDidSaveNotification:notification]; + + /** + * This loop is needed for 'correct' behaviour of NSFetchedResultsControllers. + * + * NSManagedObjectContext doesn't event fire + * NSManagedObjectContextObjectsDidChangeNotification for updated objects on merge, + * only inserted. + * + * SEE: + * http://stackoverflow.com/questions/3923826/nsfetchedresultscontroller-with-predicate-ignores-changes-merged-from-different + * May also have memory implications. + */ + for (NSManagedObject *object in [[notification userInfo] objectForKey:NSUpdatedObjectsKey]) + { + [[mainContext objectWithID:[object objectID]] willAccessValueForKey:nil]; + } + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:NSManagedObjectContextDidSaveNotification + object:nil]; + + dispatch_semaphore_signal(mainContextSavedSemaphore); + } + }]; + }; + + /** + * If this operation is being run on the main thread/queue, just merge. + * Otherwise, stall the current queue until merge is completed. + * + * In production code these operations should not be run on the main thread. + * This situation should only occur when running tests. + */ + if ([NSThread isMainThread] || dispatch_get_main_queue() == dispatch_get_current_queue()) + { + mergeBlock(); + } + else + { + //Ensure mainContext is accessed on the main thread. + dispatch_async(dispatch_get_main_queue(), mergeBlock); + dispatch_semaphore_wait(mainContextSavedSemaphore, DISPATCH_TIME_FOREVER); + } + + /** * Finished is called on the same thread that the operation is on * once the semaphore has recived it's notification. */ - [self finishOperation]; + [self finishOperation]; } - (void)dealloc diff --git a/Project/Launch Screen.storyboard b/Project/Launch Screen.storyboard new file mode 100644 index 0000000..074e668 --- /dev/null +++ b/Project/Launch Screen.storyboard @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Project/Podfile.lock b/Project/Podfile.lock index 345187a..7a4d245 100644 --- a/Project/Podfile.lock +++ b/Project/Podfile.lock @@ -1,19 +1,19 @@ PODS: - OCMock (2.2.4) - - SQKDataKit/ContextManager (0.6.0): + - SQKDataKit/ContextManager (1.1.1): - SQKDataKit/Core - - SQKDataKit/Core (0.6.0) - - SQKDataKit/CoreDataOperation (0.6.0): + - SQKDataKit/Core (1.1.1) + - SQKDataKit/CoreDataOperation (1.1.1): - SQKDataKit/ContextManager - SQKDataKit/Core - SQKDataKit/ManagedObjectExtensions - - SQKDataKit/FetchedCollectionViewController (0.6.0): + - SQKDataKit/FetchedCollectionViewController (1.1.1): - SQKDataKit/Core - - SQKDataKit/FetchedTableViewController (0.6.0): + - SQKDataKit/FetchedTableViewController (1.1.1): - SQKDataKit/Core - - SQKDataKit/ManagedObjectController (0.6.0): + - SQKDataKit/ManagedObjectController (1.1.1): - SQKDataKit/Core - - SQKDataKit/ManagedObjectExtensions (0.6.0): + - SQKDataKit/ManagedObjectExtensions (1.1.1): - SQKDataKit/Core DEPENDENCIES: @@ -31,6 +31,6 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: OCMock: a6a7dc0e3997fb9f35d99f72528698ebf60d64f2 - SQKDataKit: c3e7196e712ed7ab2997b64ff11bdedb5b0eaf51 + SQKDataKit: b906fc7ca22c35a3b54f487e218d1544a0509169 COCOAPODS: 0.39.0 diff --git a/Project/SQKDataKit.xcodeproj/project.pbxproj b/Project/SQKDataKit.xcodeproj/project.pbxproj index 67c4939..0828ee6 100644 --- a/Project/SQKDataKit.xcodeproj/project.pbxproj +++ b/Project/SQKDataKit.xcodeproj/project.pbxproj @@ -43,6 +43,12 @@ AFFC25DF40274C52A419927E /* libPods-SQKDataKit.a in Frameworks */ = {isa = PBXBuildFile; fileRef = B2868235566F44D78765833F /* libPods-SQKDataKit.a */; }; C8CFF472097F4E5BBA949BDB /* libPods-SQKDataKitTests.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 4AB52E80206042BCB848D249 /* libPods-SQKDataKitTests.a */; }; CB40EDD41A65633900181627 /* SQKAlternateCommitsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CB40EDD31A65633900181627 /* SQKAlternateCommitsViewController.m */; }; + CBFC91CB1CD206DB00444C39 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBFC91CA1CD206DB00444C39 /* Launch Screen.storyboard */; }; + CBFC91CD1CD2076500444C39 /* Storyboard.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CBFC91CC1CD2076500444C39 /* Storyboard.storyboard */; }; + CBFC91D01CD20C9100444C39 /* SQKStoryboardCommitsViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBFC91CF1CD20C9100444C39 /* SQKStoryboardCommitsViewController.m */; }; + CBFC91D41CD21CD400444C39 /* SQKStoryboardCommitsCollectionViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = CBFC91D31CD21CD400444C39 /* SQKStoryboardCommitsCollectionViewController.m */; }; + CBFC91D71CD21F2E00444C39 /* SQKStoryboardCollectionViewCell.m in Sources */ = {isa = PBXBuildFile; fileRef = CBFC91D61CD21F2E00444C39 /* SQKStoryboardCollectionViewCell.m */; }; + CBFC91DA1CD221C900444C39 /* SQKStoryboardCollectionHeaderView.m in Sources */ = {isa = PBXBuildFile; fileRef = CBFC91D91CD221C900444C39 /* SQKStoryboardCollectionHeaderView.m */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -115,6 +121,16 @@ B2868235566F44D78765833F /* libPods-SQKDataKit.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-SQKDataKit.a"; sourceTree = BUILT_PRODUCTS_DIR; }; CB40EDD21A65633900181627 /* SQKAlternateCommitsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQKAlternateCommitsViewController.h; sourceTree = ""; }; CB40EDD31A65633900181627 /* SQKAlternateCommitsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQKAlternateCommitsViewController.m; sourceTree = ""; }; + CBFC91CA1CD206DB00444C39 /* Launch Screen.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; name = "Launch Screen.storyboard"; path = "../Launch Screen.storyboard"; sourceTree = ""; }; + CBFC91CC1CD2076500444C39 /* Storyboard.storyboard */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = file.storyboard; path = Storyboard.storyboard; sourceTree = ""; }; + CBFC91CE1CD20C9100444C39 /* SQKStoryboardCommitsViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQKStoryboardCommitsViewController.h; sourceTree = ""; }; + CBFC91CF1CD20C9100444C39 /* SQKStoryboardCommitsViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQKStoryboardCommitsViewController.m; sourceTree = ""; }; + CBFC91D21CD21CD400444C39 /* SQKStoryboardCommitsCollectionViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQKStoryboardCommitsCollectionViewController.h; sourceTree = ""; }; + CBFC91D31CD21CD400444C39 /* SQKStoryboardCommitsCollectionViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQKStoryboardCommitsCollectionViewController.m; sourceTree = ""; }; + CBFC91D51CD21F2E00444C39 /* SQKStoryboardCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQKStoryboardCollectionViewCell.h; sourceTree = ""; }; + CBFC91D61CD21F2E00444C39 /* SQKStoryboardCollectionViewCell.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQKStoryboardCollectionViewCell.m; sourceTree = ""; }; + CBFC91D81CD221C900444C39 /* SQKStoryboardCollectionHeaderView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SQKStoryboardCollectionHeaderView.h; sourceTree = ""; }; + CBFC91D91CD221C900444C39 /* SQKStoryboardCollectionHeaderView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SQKStoryboardCollectionHeaderView.m; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -201,6 +217,7 @@ A3DC7C9A1868731100C45DB0 /* SQKJSONLoader.m */, CB40EDD21A65633900181627 /* SQKAlternateCommitsViewController.h */, CB40EDD31A65633900181627 /* SQKAlternateCommitsViewController.m */, + CBFC91D11CD2119C00444C39 /* Storyboard Example */, ); name = Controller; sourceTree = ""; @@ -266,6 +283,7 @@ A34BF3A1184F41D300F2E3B4 /* Supporting Files */ = { isa = PBXGroup; children = ( + CBFC91CA1CD206DB00444C39 /* Launch Screen.storyboard */, A34BF3A2184F41D300F2E3B4 /* SQKDataKit-Info.plist */, A34BF3A3184F41D300F2E3B4 /* InfoPlist.strings */, A34BF3A6184F41D300F2E3B4 /* main.m */, @@ -314,6 +332,22 @@ name = Mocks; sourceTree = ""; }; + CBFC91D11CD2119C00444C39 /* Storyboard Example */ = { + isa = PBXGroup; + children = ( + CBFC91CE1CD20C9100444C39 /* SQKStoryboardCommitsViewController.h */, + CBFC91CF1CD20C9100444C39 /* SQKStoryboardCommitsViewController.m */, + CBFC91D21CD21CD400444C39 /* SQKStoryboardCommitsCollectionViewController.h */, + CBFC91D31CD21CD400444C39 /* SQKStoryboardCommitsCollectionViewController.m */, + CBFC91D51CD21F2E00444C39 /* SQKStoryboardCollectionViewCell.h */, + CBFC91D61CD21F2E00444C39 /* SQKStoryboardCollectionViewCell.m */, + CBFC91D81CD221C900444C39 /* SQKStoryboardCollectionHeaderView.h */, + CBFC91D91CD221C900444C39 /* SQKStoryboardCollectionHeaderView.m */, + CBFC91CC1CD2076500444C39 /* Storyboard.storyboard */, + ); + name = "Storyboard Example"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -399,7 +433,9 @@ files = ( A34BF3B6184F41D400F2E3B4 /* Images.xcassets in Resources */, A3DC7CA018687E2E00C45DB0 /* data_1500.json in Resources */, + CBFC91CB1CD206DB00444C39 /* Launch Screen.storyboard in Resources */, A34BF3A5184F41D300F2E3B4 /* InfoPlist.strings in Resources */, + CBFC91CD1CD2076500444C39 /* Storyboard.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -513,13 +549,17 @@ files = ( 2289468119BF27F900289506 /* User.m in Sources */, 4AD576CA1A5DA58C00EA277A /* SQKCommitsCollectionViewController.m in Sources */, + CBFC91D01CD20C9100444C39 /* SQKStoryboardCommitsViewController.m in Sources */, A3F70A8D1859D80C00C0A389 /* SQKCommitCell.m in Sources */, A3C66D06185A20CE007AE9B5 /* NaiveImportOperation.m in Sources */, + CBFC91D71CD21F2E00444C39 /* SQKStoryboardCollectionViewCell.m in Sources */, 4AD576CD1A5DAB2D00EA277A /* SQKCommitItemCell.m in Sources */, CB40EDD41A65633900181627 /* SQKAlternateCommitsViewController.m in Sources */, + CBFC91D41CD21CD400444C39 /* SQKStoryboardCommitsCollectionViewController.m in Sources */, A34BF3B4184F41D400F2E3B4 /* SQKCommitsViewController.m in Sources */, 22780673196AB3DE00F8DED4 /* SQKCommitDetailViewController.m in Sources */, A31AB0E71857769D00B240AB /* Commit.m in Sources */, + CBFC91DA1CD221C900444C39 /* SQKStoryboardCollectionHeaderView.m in Sources */, A328BAC71850986A00267F2E /* SQKDataKitModel.xcdatamodeld in Sources */, A34BF3AB184F41D300F2E3B4 /* SQKAppDelegate.m in Sources */, A31AB0EC185776AE00B240AB /* OptimisedImportOperation.m in Sources */, @@ -652,7 +692,6 @@ baseConfigurationReference = 13CD1E4097C4550C3CDE9FF2 /* Pods-SQKDataKit.debug.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CODE_SIGN_IDENTITY = "iPhone Developer"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SQKDataKit/SQKDataKit-Prefix.pch"; @@ -671,7 +710,6 @@ baseConfigurationReference = 36C20EE916F9895AE5B1716D /* Pods-SQKDataKit.release.xcconfig */; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME = LaunchImage; CODE_SIGN_IDENTITY = "iPhone Distribution"; GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = "SQKDataKit/SQKDataKit-Prefix.pch"; diff --git a/Project/SQKDataKit.xcworkspace/xcshareddata/SQKDataKit.xcscmblueprint b/Project/SQKDataKit.xcworkspace/xcshareddata/SQKDataKit.xcscmblueprint new file mode 100644 index 0000000..5143cd4 --- /dev/null +++ b/Project/SQKDataKit.xcworkspace/xcshareddata/SQKDataKit.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "D3BB94670B6CEF9A8A4C6FAA1DBE0D5025BFEF3A", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "D3BB94670B6CEF9A8A4C6FAA1DBE0D5025BFEF3A" : 0, + "EE10C43E6C8BE2BC68765330A89116EDBFE39DB3" : 0 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "21432975-63D2-4331-A055-18E9629DE108", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "D3BB94670B6CEF9A8A4C6FAA1DBE0D5025BFEF3A" : "SQKDataKit\/", + "EE10C43E6C8BE2BC68765330A89116EDBFE39DB3" : ".." + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "SQKDataKit", + "DVTSourceControlWorkspaceBlueprintVersion" : 204, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "Project\/SQKDataKit.xcworkspace", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:3squared\/SQKDataKit.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "D3BB94670B6CEF9A8A4C6FAA1DBE0D5025BFEF3A" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "git.3squared.com:sir-robert-mcalpine\/Site-Companion-iOS.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "EE10C43E6C8BE2BC68765330A89116EDBFE39DB3" + } + ] +} \ No newline at end of file diff --git a/Project/SQKDataKit/SQKAppDelegate.h b/Project/SQKDataKit/SQKAppDelegate.h index b513704..9b3a906 100644 --- a/Project/SQKDataKit/SQKAppDelegate.h +++ b/Project/SQKDataKit/SQKAppDelegate.h @@ -13,4 +13,6 @@ @property (nonatomic, strong) UIWindow *window; +- (SQKContextManager *)contextManager; + @end diff --git a/Project/SQKDataKit/SQKAppDelegate.m b/Project/SQKDataKit/SQKAppDelegate.m index aa4fbe2..9ced9b6 100644 --- a/Project/SQKDataKit/SQKAppDelegate.m +++ b/Project/SQKDataKit/SQKAppDelegate.m @@ -6,12 +6,12 @@ // Copyright (c) 2013 3Squared. All rights reserved. // +#import "SQKAlternateCommitsViewController.h" #import "SQKAppDelegate.h" -#import "SQKCommitsViewController.h" -#import "SQKCommitsCollectionViewController.h" #import "SQKCollectionViewFlowLayout.h" +#import "SQKCommitsCollectionViewController.h" +#import "SQKCommitsViewController.h" #import "SQKMetricsViewController.h" -#import "SQKAlternateCommitsViewController.h" #import @interface SQKAppDelegate () @@ -57,8 +57,12 @@ - (BOOL)application:(UIApplication *)application UINavigationController *altCommitsNavController = [[UINavigationController alloc] initWithRootViewController:altCommitsViewController]; + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil]; + UIViewController *storyboardTableViewController = [storyboard instantiateViewControllerWithIdentifier:@"Table"]; + UIViewController *storyboardCollectionViewController = [storyboard instantiateViewControllerWithIdentifier:@"Collection"]; + UITabBarController *tabBarController = [[UITabBarController alloc] init]; - tabBarController.viewControllers = @[ metricsNavController, commitsNavController, altCommitsNavController, commitsCollectionNavController ]; + tabBarController.viewControllers = @[ metricsNavController, commitsNavController, altCommitsNavController, commitsCollectionNavController, storyboardTableViewController, storyboardCollectionViewController ]; self.window.rootViewController = tabBarController; [self.window makeKeyAndVisible]; diff --git a/Project/SQKDataKit/SQKDataKit-Info.plist b/Project/SQKDataKit/SQKDataKit-Info.plist index 7d8d54c..5b2f209 100644 --- a/Project/SQKDataKit/SQKDataKit-Info.plist +++ b/Project/SQKDataKit/SQKDataKit-Info.plist @@ -24,6 +24,8 @@ 1.1.1 LSRequiresIPhoneOS + UILaunchStoryboardName + Launch Screen UIRequiredDeviceCapabilities armv7 diff --git a/Project/SQKDataKit/SQKStoryboardCollectionHeaderView.h b/Project/SQKDataKit/SQKStoryboardCollectionHeaderView.h new file mode 100644 index 0000000..3b720a5 --- /dev/null +++ b/Project/SQKDataKit/SQKStoryboardCollectionHeaderView.h @@ -0,0 +1,13 @@ +// +// SQKStoryboardCollectionHeaderView.h +// SQKDataKit +// +// Created by Sam Oakley on 28/04/2016. +// Copyright © 2016 3Squared. All rights reserved. +// + +#import + +@interface SQKStoryboardCollectionHeaderView : UICollectionReusableView +@property (weak, nonatomic) IBOutlet UILabel *textLabel; +@end diff --git a/Project/SQKDataKit/SQKStoryboardCollectionHeaderView.m b/Project/SQKDataKit/SQKStoryboardCollectionHeaderView.m new file mode 100644 index 0000000..1b3ca32 --- /dev/null +++ b/Project/SQKDataKit/SQKStoryboardCollectionHeaderView.m @@ -0,0 +1,13 @@ +// +// SQKStoryboardCollectionHeaderView.m +// SQKDataKit +// +// Created by Sam Oakley on 28/04/2016. +// Copyright © 2016 3Squared. All rights reserved. +// + +#import "SQKStoryboardCollectionHeaderView.h" + +@implementation SQKStoryboardCollectionHeaderView + +@end diff --git a/Project/SQKDataKit/SQKStoryboardCollectionViewCell.h b/Project/SQKDataKit/SQKStoryboardCollectionViewCell.h new file mode 100644 index 0000000..ba6d4c2 --- /dev/null +++ b/Project/SQKDataKit/SQKStoryboardCollectionViewCell.h @@ -0,0 +1,13 @@ +// +// SQKStoryboardCollectionViewCell.h +// SQKDataKit +// +// Created by Sam Oakley on 28/04/2016. +// Copyright © 2016 3Squared. All rights reserved. +// + +#import + +@interface SQKStoryboardCollectionViewCell : UICollectionViewCell +@property (weak, nonatomic) IBOutlet UILabel *textLabel; +@end diff --git a/Project/SQKDataKit/SQKStoryboardCollectionViewCell.m b/Project/SQKDataKit/SQKStoryboardCollectionViewCell.m new file mode 100644 index 0000000..923f1b1 --- /dev/null +++ b/Project/SQKDataKit/SQKStoryboardCollectionViewCell.m @@ -0,0 +1,13 @@ +// +// SQKStoryboardCollectionViewCell.m +// SQKDataKit +// +// Created by Sam Oakley on 28/04/2016. +// Copyright © 2016 3Squared. All rights reserved. +// + +#import "SQKStoryboardCollectionViewCell.h" + +@implementation SQKStoryboardCollectionViewCell + +@end diff --git a/Project/SQKDataKit/SQKStoryboardCommitsCollectionViewController.h b/Project/SQKDataKit/SQKStoryboardCommitsCollectionViewController.h new file mode 100644 index 0000000..5f3ee73 --- /dev/null +++ b/Project/SQKDataKit/SQKStoryboardCommitsCollectionViewController.h @@ -0,0 +1,13 @@ +// +// SQKStoryboardCommitsCollectionViewController.h +// SQKDataKit +// +// Created by Sam Oakley on 28/04/2016. +// Copyright © 2016 3Squared. All rights reserved. +// + +#import + +@interface SQKStoryboardCommitsCollectionViewController : SQKFetchedCollectionViewController + +@end diff --git a/Project/SQKDataKit/SQKStoryboardCommitsCollectionViewController.m b/Project/SQKDataKit/SQKStoryboardCommitsCollectionViewController.m new file mode 100644 index 0000000..a9187a7 --- /dev/null +++ b/Project/SQKDataKit/SQKStoryboardCommitsCollectionViewController.m @@ -0,0 +1,103 @@ +// +// SQKStoryboardCommitsCollectionViewController.m +// SQKDataKit +// +// Created by Sam Oakley on 28/04/2016. +// Copyright © 2016 3Squared. All rights reserved. +// + +#import "Commit.h" +#import "SQKAppDelegate.h" +#import "SQKStoryboardCollectionHeaderView.h" +#import "SQKStoryboardCollectionViewCell.h" +#import "SQKStoryboardCommitsCollectionViewController.h" +#import +#import +#import + +@interface SQKStoryboardCommitsCollectionViewController () + +@end + +@implementation SQKStoryboardCommitsCollectionViewController + +#pragma mark - + +- (NSManagedObjectContext *)managedObjectContext +{ + return [((SQKAppDelegate *)[[UIApplication sharedApplication] delegate]).contextManager mainContext]; +} + +- (NSFetchRequest *)fetchRequestForSearch:(NSString *)searchString +{ + NSFetchRequest *request = [Commit sqk_fetchRequest]; + request.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"authorName" ascending:YES] ]; + + NSPredicate *filterPredicate = nil; + if (searchString.length) + { + filterPredicate = [NSPredicate predicateWithFormat:@"authorName CONTAINS[cd] %@", searchString]; + } + + [request setPredicate:filterPredicate]; + + return request; +} + +- (void)fetchedResultsController:(NSFetchedResultsController *)fetchedResultsController configureItemCell:(UICollectionViewCell *)theItemCell atIndexPath:(NSIndexPath *)indexPath +{ + Commit *commit = [fetchedResultsController objectAtIndexPath:indexPath]; + SQKStoryboardCollectionViewCell *cell = (SQKStoryboardCollectionViewCell *)theItemCell; + cell.textLabel.text = [[self firstCharactersForString:commit.authorName] uppercaseString]; +} + +- (UICollectionViewCell *)collectionView:(UICollectionView *)collectionView cellForItemAtIndexPath:(NSIndexPath *)indexPath +{ + UICollectionViewCell *itemCell = [collectionView dequeueReusableCellWithReuseIdentifier:@"Cell" forIndexPath:indexPath]; + return itemCell; +} + +- (void)collectionView:(UICollectionView *)collectionView willDisplayCell:(UICollectionViewCell *)cell forItemAtIndexPath:(NSIndexPath *)indexPath +{ + [self fetchedResultsController:self.fetchedResultsController + configureItemCell:cell + atIndexPath:indexPath]; +} + +- (NSString *)sectionKeyPathForSearchableFetchedResultsController:(SQKFetchedCollectionViewController *)controller +{ + return @"authorName"; +} + +- (UIEdgeInsets)collectionView:(UICollectionView *)collectionView layout:(UICollectionViewLayout *)collectionViewLayout insetForSectionAtIndex:(NSInteger)section +{ + return UIEdgeInsetsMake(10, 26, 10, 26); +} + +- (UICollectionReusableView *)collectionView:(UICollectionView *)collectionView viewForSupplementaryElementOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath +{ + if (kind == UICollectionElementKindSectionHeader) + { + SQKStoryboardCollectionHeaderView *view = (SQKStoryboardCollectionHeaderView *)[self.collectionView dequeueReusableSupplementaryViewOfKind:kind withReuseIdentifier:@"Header" forIndexPath:indexPath]; + id section = self.fetchedResultsController.sections[indexPath.section]; + view.textLabel.text = [section name]; + return view; + } + return nil; +} + +- (NSString *)firstCharactersForString:(NSString *)string +{ + NSMutableString *firstCharacters = [NSMutableString string]; + + NSArray *words = [string componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; + + [words enumerateObjectsUsingBlock:^(NSString *word, NSUInteger idx, BOOL *stop) { + NSString *firstLetter = [word substringToIndex:1]; + [firstCharacters appendString:firstLetter]; + }]; + + return firstCharacters; +} + +@end diff --git a/Project/SQKDataKit/SQKStoryboardCommitsViewController.h b/Project/SQKDataKit/SQKStoryboardCommitsViewController.h new file mode 100644 index 0000000..7788a5d --- /dev/null +++ b/Project/SQKDataKit/SQKStoryboardCommitsViewController.h @@ -0,0 +1,14 @@ +// +// SQKStoryboardCommitsViewController.h +// SQKDataKit +// +// Created by Sam Oakley on 28/04/2016. +// Copyright © 2016 3Squared. All rights reserved. +// + +#import +#import + +@interface SQKStoryboardCommitsViewController : SQKFetchedTableViewController + +@end diff --git a/Project/SQKDataKit/SQKStoryboardCommitsViewController.m b/Project/SQKDataKit/SQKStoryboardCommitsViewController.m new file mode 100644 index 0000000..6773891 --- /dev/null +++ b/Project/SQKDataKit/SQKStoryboardCommitsViewController.m @@ -0,0 +1,59 @@ +// +// SQKStoryboardCommitsViewController.m +// SQKDataKit +// +// Created by Sam Oakley on 28/04/2016. +// Copyright © 2016 3Squared. All rights reserved. +// + +#import "Commit.h" +#import "SQKAppDelegate.h" +#import "SQKStoryboardCommitsViewController.h" +#import +#import + +@interface SQKStoryboardCommitsViewController () + +@end + +@implementation SQKStoryboardCommitsViewController + +- (NSManagedObjectContext *)managedObjectContext +{ + return [((SQKAppDelegate *)[[UIApplication sharedApplication] delegate]).contextManager mainContext]; +} + +#pragma mark - + +- (NSFetchRequest *)fetchRequestForSearch:(NSString *)searchString +{ + NSFetchRequest *request = [Commit sqk_fetchRequest]; + request.sortDescriptors = @[ [NSSortDescriptor sortDescriptorWithKey:@"date" ascending:NO] ]; + + NSPredicate *filterPredicate = nil; + if (searchString.length) + { + filterPredicate = [NSPredicate predicateWithFormat:@"message CONTAINS[cd] %@", searchString]; + } + + [request setPredicate:filterPredicate]; + + return request; +} + +- (void)fetchedResultsController:(NSFetchedResultsController *)fetchedResultsController + configureCell:(UITableViewCell *)cell + atIndexPath:(NSIndexPath *)indexPath +{ + Commit *commit = [fetchedResultsController objectAtIndexPath:indexPath]; + cell.textLabel.text = commit.message; +} + +- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"Cell" forIndexPath:indexPath]; + [self fetchedResultsController:[self activeFetchedResultsController] configureCell:cell atIndexPath:indexPath]; + return cell; +} + +@end diff --git a/Project/SQKDataKit/Storyboard.storyboard b/Project/SQKDataKit/Storyboard.storyboard new file mode 100644 index 0000000..b2efc14 --- /dev/null +++ b/Project/SQKDataKit/Storyboard.storyboard @@ -0,0 +1,185 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +