From d5206fd52fc1001c6d7a297fc5b4c01ef2008c63 Mon Sep 17 00:00:00 2001 From: David Yates Date: Fri, 22 Apr 2016 13:42:35 +0100 Subject: [PATCH 01/16] Make subclass of UICollectionViewController --- .../SQKFetchedCollectionViewController.h | 5 +- .../SQKFetchedCollectionViewController.m | 247 +++++++++--------- 2 files changed, 120 insertions(+), 132 deletions(-) diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h index 226e05c..d980562 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h @@ -10,7 +10,7 @@ @import CoreData; @import UIKit; -@interface SQKFetchedCollectionViewController : UIViewController +@interface SQKFetchedCollectionViewController : UICollectionViewController /** * Initialises a Core Data-backed UICollectionViewController with a search bar. @@ -36,8 +36,7 @@ /** * The collection view shown by the view controller. */ -@property (strong, nonatomic, readonly) UICollectionView *collectionView; -@property (strong, nonatomic, readonly) UICollectionViewLayout *collectionViewLayout; +@property (strong, nonatomic) UICollectionViewLayout *collectionViewLayout; /** * An optional refresh control shown when pulling down the collectionview. diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m index 8677da9..fb481fe 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m @@ -23,9 +23,6 @@ @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 @@ -68,13 +65,9 @@ - (instancetype)initWithCoder:(NSCoder *)coder return self; } -- (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.searchingEnabled) { @@ -86,11 +79,6 @@ - (void)loadView self.searchBar.delegate = self; [self.collectionView addSubview:self.searchBar]; } -} - -- (void)viewDidLoad -{ - [super viewDidLoad]; self.fetchedResultsController = [self fetchedResultsControllerWithSearch:nil]; @@ -176,51 +164,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 +236,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 +246,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 +291,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 +430,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; From 91f0b43c14684b5bc699d28c76c36c2f274df8ae Mon Sep 17 00:00:00 2001 From: David Yates Date: Fri, 22 Apr 2016 13:55:16 +0100 Subject: [PATCH 02/16] Fix assigning of collectionViewLayout --- .../SQKFetchedCollectionViewController.h | 5 ----- .../SQKFetchedCollectionViewController.m | 4 ++-- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h index d980562..9d2ec24 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h @@ -33,11 +33,6 @@ */ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context searchingEnabled:(BOOL)searchingEnabled; -/** - * The collection view shown by the view controller. - */ -@property (strong, nonatomic) UICollectionViewLayout *collectionViewLayout; - /** * An optional refresh control shown when pulling down the collectionview. * Set this in your subclass. diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m index fb481fe..5f1dcc5 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m @@ -34,7 +34,7 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout co { self.managedObjectContext = context; self.searchingEnabled = YES; - self.collectionViewLayout = layout; + self.collectionView.collectionViewLayout = layout; } return self; @@ -48,7 +48,7 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout co { self.managedObjectContext = context; self.searchingEnabled = searchingEnabled; - self.collectionViewLayout = layout; + self.collectionView.collectionViewLayout = layout; } return self; From d2002ea6720732ff91050342a1ac7b9fc510bcaf Mon Sep 17 00:00:00 2001 From: David Yates Date: Fri, 22 Apr 2016 14:03:06 +0100 Subject: [PATCH 03/16] Setup collection view layout --- .../SQKFetchedCollectionViewController.h | 5 +++++ .../SQKFetchedCollectionViewController.m | 8 ++++++-- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h index 9d2ec24..412d770 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h @@ -33,6 +33,11 @@ */ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context searchingEnabled:(BOOL)searchingEnabled; +/** + * The collection view shown by the view controller. + */ +@property (strong, nonatomic) UICollectionViewLayout *layout; + /** * An optional refresh control shown when pulling down the collectionview. * Set this in your subclass. diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m index 5f1dcc5..e85949c 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m @@ -34,7 +34,7 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout co { self.managedObjectContext = context; self.searchingEnabled = YES; - self.collectionView.collectionViewLayout = layout; + self.layout = layout; } return self; @@ -48,7 +48,7 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout co { self.managedObjectContext = context; self.searchingEnabled = searchingEnabled; - self.collectionView.collectionViewLayout = layout; + self.layout = layout; } return self; @@ -61,6 +61,8 @@ - (instancetype)initWithCoder:(NSCoder *)coder if (self) { self.searchingEnabled = YES; + + self.layout = [[UICollectionViewFlowLayout alloc] init]; } return self; } @@ -69,6 +71,8 @@ - (void)viewDidLoad { [super viewDidLoad]; + self.collectionView.collectionViewLayout = self.layout; + if (self.searchingEnabled) { self.edgesForExtendedLayout = UIRectEdgeNone; From af08dda8dd61f042b0ce51cf1b53894db38b674f Mon Sep 17 00:00:00 2001 From: David Yates Date: Fri, 22 Apr 2016 14:06:25 +0100 Subject: [PATCH 04/16] Add loadView method back --- .../SQKFetchedCollectionViewController.m | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m index e85949c..92c658d 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m @@ -67,6 +67,24 @@ - (instancetype)initWithCoder:(NSCoder *)coder return self; } +- (void)loadView +{ + self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.layout]; + self.collectionView.delegate = self; + self.collectionView.dataSource = self; + + if (self.searchingEnabled) + { + self.edgesForExtendedLayout = UIRectEdgeNone; + + self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, -44, CGRectGetWidth(self.collectionView.frame), 44)]; + self.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; + self.searchBar.autocorrectionType = UITextAutocorrectionTypeNo; + self.searchBar.delegate = self; + [self.collectionView addSubview:self.searchBar]; + } +} + - (void)viewDidLoad { [super viewDidLoad]; From bdd29765adfd7eb30e7472814f8bf5e626506035 Mon Sep 17 00:00:00 2001 From: David Yates Date: Fri, 22 Apr 2016 14:11:57 +0100 Subject: [PATCH 05/16] Comment out loadView --- .../SQKFetchedCollectionViewController.m | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m index 92c658d..cd04cef 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m @@ -67,23 +67,23 @@ - (instancetype)initWithCoder:(NSCoder *)coder return self; } -- (void)loadView -{ - self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.layout]; - self.collectionView.delegate = self; - self.collectionView.dataSource = self; - - if (self.searchingEnabled) - { - self.edgesForExtendedLayout = UIRectEdgeNone; - - self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, -44, CGRectGetWidth(self.collectionView.frame), 44)]; - self.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; - self.searchBar.autocorrectionType = UITextAutocorrectionTypeNo; - self.searchBar.delegate = self; - [self.collectionView addSubview:self.searchBar]; - } -} +//- (void)loadView +//{ +// self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.layout]; +// self.collectionView.delegate = self; +// self.collectionView.dataSource = self; +// +// if (self.searchingEnabled) +// { +// self.edgesForExtendedLayout = UIRectEdgeNone; +// +// self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, -44, CGRectGetWidth(self.collectionView.frame), 44)]; +// self.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; +// self.searchBar.autocorrectionType = UITextAutocorrectionTypeNo; +// self.searchBar.delegate = self; +// [self.collectionView addSubview:self.searchBar]; +// } +//} - (void)viewDidLoad { From c698da2d1ac4d259f3fae297977d893958718a32 Mon Sep 17 00:00:00 2001 From: David Yates Date: Fri, 22 Apr 2016 14:15:39 +0100 Subject: [PATCH 06/16] Call initWithCollectionViewLayout --- .../SQKFetchedCollectionViewController.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m index cd04cef..c190493 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m @@ -29,7 +29,7 @@ @implementation SQKFetchedCollectionViewController - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context { - self = [super init]; + self = [super initWithCollectionViewLayout:layout]; if (self) { self.managedObjectContext = context; @@ -42,7 +42,7 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout co - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context searchingEnabled:(BOOL)searchingEnabled { - self = [super init]; + self = [super initWithCollectionViewLayout:layout]; if (self) { From e5d898b918d3e2698ee6f7f8ce6311adc0b26261 Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Thu, 28 Apr 2016 11:06:26 +0100 Subject: [PATCH 07/16] Add launch screen for big screen compatibility --- Project/Launch Screen.storyboard | 50 ++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 Project/Launch Screen.storyboard 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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 9a7d9458d59bcd7f0325609d033b7f8fd453df60 Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Thu, 28 Apr 2016 11:07:54 +0100 Subject: [PATCH 08/16] Extend tableView controller from viewController - allowing more flexibility in storyboards --- .../SQKFetchedTableViewController.h | 14 +- .../SQKFetchedTableViewController.m | 171 +++++++++--------- 2 files changed, 95 insertions(+), 90 deletions(-) diff --git a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h index a49a224..0f80ae3 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h @@ -14,8 +14,9 @@ * 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 + : UIViewController /** * Initialises a Core Data-backed UITableViewController with a configured with a @@ -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,14 @@ */ @property (strong, nonatomic, readonly) UISearchDisplayController *searchController; -@property (strong, nonatomic) UIView *emptyView; +@property (strong, nonatomic) IBOutlet UIView *emptyView; + +/** + * The base UITableView, for regular results. + */ +@property (strong, nonatomic) IBOutlet UITableView *tableView; + +@property (strong, nonatomic) UIRefreshControl *refreshControl; /** * 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..26530b5 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m @@ -21,6 +21,7 @@ @interface SQKFetchedTableViewController () @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController; @property (strong, nonatomic) NSFetchedResultsController *searchFetchedResultsController; @property (nonatomic, assign, readwrite) BOOL searchIsActive; +@property (nonatomic, assign) UITableViewStyle style; @end @implementation SQKFetchedTableViewController @@ -37,7 +38,7 @@ - (instancetype)initWithContext:(NSManagedObjectContext *)managedObjectContext searchingEnabled:(BOOL)searchingEnabled style:(UITableViewStyle)style { - if (self = [super initWithStyle:style]) + if (self = [super init]) { self.managedObjectContext = managedObjectContext; self.searchingEnabled = searchingEnabled; @@ -45,20 +46,22 @@ - (instancetype)initWithContext:(NSManagedObjectContext *)managedObjectContext return self; } -- (instancetype)initWithCoder:(NSCoder *)coder +- (void)viewDidLoad { - self = [super initWithCoder:coder]; + [super viewDidLoad]; - if (self) + if (!self.tableView) { - self.searchingEnabled = YES; + self.tableView = [[UITableView alloc] init]; + self.tableView.delegate = self; + self.tableView.dataSource = self; + self.view = self.tableView; } - return self; -} -- (void)viewDidLoad -{ - [super viewDidLoad]; + if (self.refreshControl) + { + [self.tableView addSubview:self.refreshControl]; + } if (self.searchingEnabled) { @@ -106,6 +109,15 @@ - (void)dealloc #pragma mark - #pragma mark Fetched results controller data source +- (void)setRefreshControl:(UIRefreshControl *)refreshControl +{ + _refreshControl = refreshControl; + if (self.isViewLoaded) + { + [self.tableView addSubview:self.refreshControl]; + } +} + - (UITableView *)activeTableView { return [self activeFetchedResultsController] == self.fetchedResultsController ? self.tableView : self.searchController.searchResultsTableView; @@ -163,93 +175,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:([[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; + } }]; } @@ -409,17 +413,10 @@ - (BOOL)searchController:(UISearchDisplayController *)controller - (void)showEmptyView:(BOOL)show { - if (_emptyView) + if (self.emptyView) { - if (show) - { - _emptyView.center = self.view.center; - [self.view addSubview:_emptyView]; - } - else - { - [self.emptyView removeFromSuperview]; - } + self.emptyView.hidden = !show; + self.tableView.scrollEnabled = !show; } } From d758d90d3e4b31cc317d78917c398d3b1b215e0b Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Thu, 28 Apr 2016 11:08:30 +0100 Subject: [PATCH 09/16] Add an example using SQKFetched from a storyboard --- Project/SQKDataKit.xcodeproj/project.pbxproj | 24 ++++- Project/SQKDataKit/SQKAppDelegate.h | 2 + Project/SQKDataKit/SQKAppDelegate.m | 11 +- Project/SQKDataKit/SQKDataKit-Info.plist | 2 + .../SQKStoryboardCommitsViewController.h | 14 +++ .../SQKStoryboardCommitsViewController.m | 59 ++++++++++ Project/SQKDataKit/Storyboard.storyboard | 101 ++++++++++++++++++ 7 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 Project/SQKDataKit/SQKStoryboardCommitsViewController.h create mode 100644 Project/SQKDataKit/SQKStoryboardCommitsViewController.m create mode 100644 Project/SQKDataKit/Storyboard.storyboard diff --git a/Project/SQKDataKit.xcodeproj/project.pbxproj b/Project/SQKDataKit.xcodeproj/project.pbxproj index 67c4939..5ec7e9b 100644 --- a/Project/SQKDataKit.xcodeproj/project.pbxproj +++ b/Project/SQKDataKit.xcodeproj/project.pbxproj @@ -43,6 +43,9 @@ 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 */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -115,6 +118,10 @@ 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 = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -201,6 +208,7 @@ A3DC7C9A1868731100C45DB0 /* SQKJSONLoader.m */, CB40EDD21A65633900181627 /* SQKAlternateCommitsViewController.h */, CB40EDD31A65633900181627 /* SQKAlternateCommitsViewController.m */, + CBFC91D11CD2119C00444C39 /* Storyboard Example */, ); name = Controller; sourceTree = ""; @@ -266,6 +274,7 @@ A34BF3A1184F41D300F2E3B4 /* Supporting Files */ = { isa = PBXGroup; children = ( + CBFC91CA1CD206DB00444C39 /* Launch Screen.storyboard */, A34BF3A2184F41D300F2E3B4 /* SQKDataKit-Info.plist */, A34BF3A3184F41D300F2E3B4 /* InfoPlist.strings */, A34BF3A6184F41D300F2E3B4 /* main.m */, @@ -314,6 +323,16 @@ name = Mocks; sourceTree = ""; }; + CBFC91D11CD2119C00444C39 /* Storyboard Example */ = { + isa = PBXGroup; + children = ( + CBFC91CE1CD20C9100444C39 /* SQKStoryboardCommitsViewController.h */, + CBFC91CF1CD20C9100444C39 /* SQKStoryboardCommitsViewController.m */, + CBFC91CC1CD2076500444C39 /* Storyboard.storyboard */, + ); + name = "Storyboard Example"; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -399,7 +418,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,6 +534,7 @@ 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 */, 4AD576CD1A5DAB2D00EA277A /* SQKCommitItemCell.m in Sources */, @@ -652,7 +674,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 +692,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/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..d82998b 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,11 @@ - (BOOL)application:(UIApplication *)application UINavigationController *altCommitsNavController = [[UINavigationController alloc] initWithRootViewController:altCommitsViewController]; + UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil]; + UIViewController *storyboardViewController = [storyboard instantiateInitialViewController]; + UITabBarController *tabBarController = [[UITabBarController alloc] init]; - tabBarController.viewControllers = @[ metricsNavController, commitsNavController, altCommitsNavController, commitsCollectionNavController ]; + tabBarController.viewControllers = @[ metricsNavController, commitsNavController, altCommitsNavController, commitsCollectionNavController, storyboardViewController ]; 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/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..280596e --- /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:@"authorName 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..b0de97e --- /dev/null +++ b/Project/SQKDataKit/Storyboard.storyboard @@ -0,0 +1,101 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 35d2315fd19246c641795d43cba9996952041ac8 Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Thu, 28 Apr 2016 12:55:10 +0100 Subject: [PATCH 10/16] Change SQKFetchedCollectionViewController to be a subclass of UIViewController This makes it possible to use it from a Storyboard. It will use the collection view setup in your scene, if linked up. If not, it will create it's own. --- .../SQKFetchedCollectionViewController.h | 12 +++- .../SQKFetchedCollectionViewController.m | 58 +++++-------------- 2 files changed, 24 insertions(+), 46 deletions(-) diff --git a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h index 412d770..96fc874 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.h @@ -10,7 +10,8 @@ @import CoreData; @import UIKit; -@interface SQKFetchedCollectionViewController : UICollectionViewController +IB_DESIGNABLE +@interface SQKFetchedCollectionViewController : UIViewController /** * Initialises a Core Data-backed UICollectionViewController with a search bar. @@ -36,6 +37,11 @@ /** * The collection view shown by the view controller. */ +@property (strong, nonatomic) IBOutlet UICollectionView *collectionView; + +/** + * The collection view layout used by the view controller. + */ @property (strong, nonatomic) UICollectionViewLayout *layout; /** @@ -51,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. @@ -67,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 c190493..b743fbf 100644 --- a/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m +++ b/Classes/ios/SQKFetchedCollectionViewController/SQKFetchedCollectionViewController.m @@ -27,22 +27,9 @@ @interface SQKFetchedCollectionViewController () @implementation SQKFetchedCollectionViewController -- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context -{ - self = [super initWithCollectionViewLayout:layout]; - if (self) - { - self.managedObjectContext = context; - self.searchingEnabled = YES; - self.layout = layout; - } - - return self; -} - - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context searchingEnabled:(BOOL)searchingEnabled { - self = [super initWithCollectionViewLayout:layout]; + self = [super init]; if (self) { @@ -54,42 +41,27 @@ - (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout co return self; } -- (instancetype)initWithCoder:(NSCoder *)coder +- (instancetype)initWithCollectionViewLayout:(UICollectionViewLayout *)layout context:(NSManagedObjectContext *)context { - self = [super initWithCoder:coder]; - - if (self) - { - self.searchingEnabled = YES; - - self.layout = [[UICollectionViewFlowLayout alloc] init]; - } - return self; + return [self initWithCollectionViewLayout:layout context:context searchingEnabled:YES]; } -//- (void)loadView -//{ -// self.collectionView = [[UICollectionView alloc] initWithFrame:CGRectZero collectionViewLayout:self.layout]; -// self.collectionView.delegate = self; -// self.collectionView.dataSource = self; -// -// if (self.searchingEnabled) -// { -// self.edgesForExtendedLayout = UIRectEdgeNone; -// -// self.searchBar = [[UISearchBar alloc] initWithFrame:CGRectMake(0, -44, CGRectGetWidth(self.collectionView.frame), 44)]; -// self.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; -// self.searchBar.autocorrectionType = UITextAutocorrectionTypeNo; -// self.searchBar.delegate = self; -// [self.collectionView addSubview:self.searchBar]; -// } -//} - - (void)viewDidLoad { [super viewDidLoad]; - self.collectionView.collectionViewLayout = self.layout; + 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) { From 836ac9cbb495097d424619d990f12c7c815e2161 Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Thu, 28 Apr 2016 12:55:21 +0100 Subject: [PATCH 11/16] Add example of collection view from Storyboard --- Project/Podfile.lock | 16 +-- Project/SQKDataKit.xcodeproj/project.pbxproj | 18 +++ .../xcshareddata/SQKDataKit.xcscmblueprint | 30 +++++ Project/SQKDataKit/SQKAppDelegate.m | 5 +- .../SQKStoryboardCollectionHeaderView.h | 13 ++ .../SQKStoryboardCollectionHeaderView.m | 13 ++ .../SQKStoryboardCollectionViewCell.h | 13 ++ .../SQKStoryboardCollectionViewCell.m | 13 ++ ...toryboardCommitsCollectionViewController.h | 13 ++ ...toryboardCommitsCollectionViewController.m | 103 +++++++++++++++ Project/SQKDataKit/Storyboard.storyboard | 123 +++++++++++++++++- 11 files changed, 347 insertions(+), 13 deletions(-) create mode 100644 Project/SQKDataKit.xcworkspace/xcshareddata/SQKDataKit.xcscmblueprint create mode 100644 Project/SQKDataKit/SQKStoryboardCollectionHeaderView.h create mode 100644 Project/SQKDataKit/SQKStoryboardCollectionHeaderView.m create mode 100644 Project/SQKDataKit/SQKStoryboardCollectionViewCell.h create mode 100644 Project/SQKDataKit/SQKStoryboardCollectionViewCell.m create mode 100644 Project/SQKDataKit/SQKStoryboardCommitsCollectionViewController.h create mode 100644 Project/SQKDataKit/SQKStoryboardCommitsCollectionViewController.m 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 5ec7e9b..0828ee6 100644 --- a/Project/SQKDataKit.xcodeproj/project.pbxproj +++ b/Project/SQKDataKit.xcodeproj/project.pbxproj @@ -46,6 +46,9 @@ 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 */ @@ -122,6 +125,12 @@ 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 */ @@ -328,6 +337,12 @@ 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"; @@ -537,11 +552,14 @@ 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 */, 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.m b/Project/SQKDataKit/SQKAppDelegate.m index d82998b..9ced9b6 100644 --- a/Project/SQKDataKit/SQKAppDelegate.m +++ b/Project/SQKDataKit/SQKAppDelegate.m @@ -58,10 +58,11 @@ - (BOOL)application:(UIApplication *)application [[UINavigationController alloc] initWithRootViewController:altCommitsViewController]; UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Storyboard" bundle:nil]; - UIViewController *storyboardViewController = [storyboard instantiateInitialViewController]; + UIViewController *storyboardTableViewController = [storyboard instantiateViewControllerWithIdentifier:@"Table"]; + UIViewController *storyboardCollectionViewController = [storyboard instantiateViewControllerWithIdentifier:@"Collection"]; UITabBarController *tabBarController = [[UITabBarController alloc] init]; - tabBarController.viewControllers = @[ metricsNavController, commitsNavController, altCommitsNavController, commitsCollectionNavController, storyboardViewController ]; + tabBarController.viewControllers = @[ metricsNavController, commitsNavController, altCommitsNavController, commitsCollectionNavController, storyboardTableViewController, storyboardCollectionViewController ]; self.window.rootViewController = tabBarController; [self.window makeKeyAndVisible]; 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/Storyboard.storyboard b/Project/SQKDataKit/Storyboard.storyboard index b0de97e..6dca661 100644 --- a/Project/SQKDataKit/Storyboard.storyboard +++ b/Project/SQKDataKit/Storyboard.storyboard @@ -1,8 +1,9 @@ - + + @@ -79,10 +80,10 @@ - + - + @@ -97,5 +98,121 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 8b1fae232637b6ad6922356c29bca9e24801dabe Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Fri, 29 Apr 2016 09:21:49 +0100 Subject: [PATCH 12/16] Add missing protocols --- .../SQKFetchedTableViewController.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h index 0f80ae3..558b3d5 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h @@ -16,7 +16,7 @@ */ IB_DESIGNABLE @interface SQKFetchedTableViewController - : UIViewController + : UIViewController /** * Initialises a Core Data-backed UITableViewController with a configured with a From f0cbec009cb6b2b24ee15a0e902e4c04e634958b Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Fri, 29 Apr 2016 09:28:39 +0100 Subject: [PATCH 13/16] Fix crash when root view from a Storyboard is UITableView --- .../SQKFetchedTableViewController.m | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m index 26530b5..2c03bbe 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m @@ -50,7 +50,11 @@ - (void)viewDidLoad { [super viewDidLoad]; - if (!self.tableView) + if ([self.view isKindOfClass:[UITableView class]]) + { + self.tableView = self.view; + } + else if (!self.tableView) { self.tableView = [[UITableView alloc] init]; self.tableView.delegate = self; From 1f9f9f12d4213b53fbc458e89fd6571aa4d63eed Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Fri, 29 Apr 2016 13:43:14 +0100 Subject: [PATCH 14/16] Revert SQKFetchedTableViewController to use UITableViewController UIRefreshControl is annoying :/ --- .../SQKFetchedTableViewController.h | 9 +- .../SQKFetchedTableViewController.m | 49 ++------ .../SQKStoryboardCommitsViewController.m | 2 +- Project/SQKDataKit/Storyboard.storyboard | 117 +++++++----------- 4 files changed, 56 insertions(+), 121 deletions(-) diff --git a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h index 558b3d5..1355c5f 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.h @@ -16,7 +16,7 @@ */ IB_DESIGNABLE @interface SQKFetchedTableViewController - : UIViewController + : UITableViewController /** * Initialises a Core Data-backed UITableViewController with a configured with a @@ -72,13 +72,6 @@ IB_DESIGNABLE @property (strong, nonatomic) IBOutlet UIView *emptyView; -/** - * The base UITableView, for regular results. - */ -@property (strong, nonatomic) IBOutlet UITableView *tableView; - -@property (strong, nonatomic) UIRefreshControl *refreshControl; - /** * Returns the currently active UITableView (i.e. regular or search). * @return The UITableView that is currently active. diff --git a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m index 2c03bbe..edb6bd1 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m @@ -21,7 +21,6 @@ @interface SQKFetchedTableViewController () @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController; @property (strong, nonatomic) NSFetchedResultsController *searchFetchedResultsController; @property (nonatomic, assign, readwrite) BOOL searchIsActive; -@property (nonatomic, assign) UITableViewStyle style; @end @implementation SQKFetchedTableViewController @@ -38,7 +37,7 @@ - (instancetype)initWithContext:(NSManagedObjectContext *)managedObjectContext searchingEnabled:(BOOL)searchingEnabled style:(UITableViewStyle)style { - if (self = [super init]) + if (self = [super initWithStyle:style]) { self.managedObjectContext = managedObjectContext; self.searchingEnabled = searchingEnabled; @@ -46,26 +45,20 @@ - (instancetype)initWithContext:(NSManagedObjectContext *)managedObjectContext return self; } -- (void)viewDidLoad +- (instancetype)initWithCoder:(NSCoder *)coder { - [super viewDidLoad]; + self = [super initWithCoder:coder]; - if ([self.view isKindOfClass:[UITableView class]]) - { - self.tableView = self.view; - } - else if (!self.tableView) + if (self) { - self.tableView = [[UITableView alloc] init]; - self.tableView.delegate = self; - self.tableView.dataSource = self; - self.view = self.tableView; + self.searchingEnabled = YES; } + return self; +} - if (self.refreshControl) - { - [self.tableView addSubview:self.refreshControl]; - } +- (void)viewDidLoad +{ + [super viewDidLoad]; if (self.searchingEnabled) { @@ -84,7 +77,7 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[self activeTableView] reloadData]; - [self showEmptyView:([[[self activeFetchedResultsController] fetchedObjects] count] == 0)]; + self.emptyView.hidden = !([[[self activeFetchedResultsController] fetchedObjects] count] == 0); } - (void)didReceiveMemoryWarning @@ -113,15 +106,6 @@ - (void)dealloc #pragma mark - #pragma mark Fetched results controller data source -- (void)setRefreshControl:(UIRefreshControl *)refreshControl -{ - _refreshControl = refreshControl; - if (self.isViewLoaded) - { - [self.tableView addSubview:self.refreshControl]; - } -} - - (UITableView *)activeTableView { return [self activeFetchedResultsController] == self.fetchedResultsController ? self.tableView : self.searchController.searchResultsTableView; @@ -232,7 +216,7 @@ - (void)controller:(NSFetchedResultsController *)controller UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchController.searchResultsTableView; - [self showEmptyView:([[controller fetchedObjects] count] == 0)]; + self.emptyView.hidden = !([[controller fetchedObjects] count] == 0); switch (type) { @@ -415,13 +399,4 @@ - (BOOL)searchController:(UISearchDisplayController *)controller return YES; } -- (void)showEmptyView:(BOOL)show -{ - if (self.emptyView) - { - self.emptyView.hidden = !show; - self.tableView.scrollEnabled = !show; - } -} - @end diff --git a/Project/SQKDataKit/SQKStoryboardCommitsViewController.m b/Project/SQKDataKit/SQKStoryboardCommitsViewController.m index 280596e..6773891 100644 --- a/Project/SQKDataKit/SQKStoryboardCommitsViewController.m +++ b/Project/SQKDataKit/SQKStoryboardCommitsViewController.m @@ -33,7 +33,7 @@ - (NSFetchRequest *)fetchRequestForSearch:(NSString *)searchString NSPredicate *filterPredicate = nil; if (searchString.length) { - filterPredicate = [NSPredicate predicateWithFormat:@"authorName CONTAINS[cd] %@", searchString]; + filterPredicate = [NSPredicate predicateWithFormat:@"message CONTAINS[cd] %@", searchString]; } [request setPredicate:filterPredicate]; diff --git a/Project/SQKDataKit/Storyboard.storyboard b/Project/SQKDataKit/Storyboard.storyboard index 6dca661..b2efc14 100644 --- a/Project/SQKDataKit/Storyboard.storyboard +++ b/Project/SQKDataKit/Storyboard.storyboard @@ -6,80 +6,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -91,13 +17,54 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + From 54881e3f5e3729c899d2aa493ecf5ab05bfa8b04 Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Tue, 3 May 2016 10:43:51 +0100 Subject: [PATCH 15/16] Prevent deadlock when the operation is being run on the main thread --- .../SQKCoreDataOperation.m | 153 ++++++++++-------- 1 file changed, 84 insertions(+), 69 deletions(-) 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 From b4eb0d1a23bbdea003d0bdb33218aab7a7a6e219 Mon Sep 17 00:00:00 2001 From: Sam Oakley Date: Thu, 9 Jun 2016 14:54:06 +0100 Subject: [PATCH 16/16] Improve empty view display and remove "No Results" text if one is supplied --- .../SQKFetchedTableViewController.m | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m index edb6bd1..df6ac7c 100644 --- a/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m +++ b/Classes/ios/SQKFetchedTableViewController/SQKFetchedTableViewController.m @@ -77,7 +77,7 @@ - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; [[self activeTableView] reloadData]; - self.emptyView.hidden = !([[[self activeFetchedResultsController] fetchedObjects] count] == 0); + [self showEmptyView:([[[self activeFetchedResultsController] fetchedObjects] count] == 0)]; } - (void)didReceiveMemoryWarning @@ -216,7 +216,7 @@ - (void)controller:(NSFetchedResultsController *)controller UITableView *tableView = controller == self.fetchedResultsController ? self.tableView : self.searchController.searchResultsTableView; - self.emptyView.hidden = !([[controller fetchedObjects] count] == 0); + [self showEmptyView:([[[self activeFetchedResultsController] fetchedObjects] count] == 0)]; switch (type) { @@ -284,6 +284,8 @@ - (NSFetchedResultsController *)newFetchedResultsControllerWithSearch:(NSString abort(); } + [self showEmptyView:([fetchedResultsController.fetchedObjects count] == 0)]; + return fetchedResultsController; } @@ -399,4 +401,43 @@ - (BOOL)searchController:(UISearchDisplayController *)controller return YES; } +- (void)showEmptyView:(BOOL)show +{ + if (_emptyView) + { + BOOL isSearching = (self.searchController && self.searchController.active); + UITableView *tableView = isSearching ? self.searchController.searchResultsTableView : self.tableView; + + if (show) + { + 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.tableView.tableHeaderView = self.searchController.searchBar; + [_emptyView removeFromSuperview]; + } + } +} + @end