From e202284e60109f329bda398d3ea1b338efe80c26 Mon Sep 17 00:00:00 2001 From: Mike Lee Date: Fri, 2 Aug 2013 16:29:29 +0200 Subject: [PATCH] # List sorting MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * The talk list and related talk view can now be sorted in ascending or descending order via a control in the navigation bar. (Refs #38) * This is accomplished with a boolean property and the sort descriptor of the fetch request. * Sort direction is saved and restored from user defaults. * The title is now the topic, i.e. the topic of the selected issue. (Refs #33) ## Refactoring * Pare down the fetch request automatic accessor to make it easier to refresh the fetch request, potentially with new data, and do so. * The reloadList: method now serves a legitimate purpose, reloading the fetch as well as the table view, which can still be reloaded directly. ## Style * Took out all that commented table view code from the talk list. * That code was replaced with the simple, naïve implementation. It remains implemented this was in the talk view controller, so we can compare the two. --- Lemacs/Base.lproj/Main_iPhone.storyboard | 34 +++++- Lemacs/LETalkListController.h | 2 + Lemacs/LETalkListController.m | 144 +++++++++-------------- Lemacs/LETalkViewController.h | 5 + Lemacs/LETalkViewController.m | 76 +++++++----- Lemacs/LEWorkViewController.m | 1 + 6 files changed, 139 insertions(+), 123 deletions(-) diff --git a/Lemacs/Base.lproj/Main_iPhone.storyboard b/Lemacs/Base.lproj/Main_iPhone.storyboard index 76baf00..ba8392d 100644 --- a/Lemacs/Base.lproj/Main_iPhone.storyboard +++ b/Lemacs/Base.lproj/Main_iPhone.storyboard @@ -115,11 +115,23 @@ - - + + + + + + + + + + + + + + - + @@ -158,13 +170,25 @@ - + + + + + + + + + + + + + @@ -188,6 +212,6 @@ - + \ No newline at end of file diff --git a/Lemacs/LETalkListController.h b/Lemacs/LETalkListController.h index 7c1876a..48ffd21 100644 --- a/Lemacs/LETalkListController.h +++ b/Lemacs/LETalkListController.h @@ -16,7 +16,9 @@ - (IBAction)reloadList; - (IBAction)resizeCells:(UIPinchGestureRecognizer *)pinch; - (IBAction)showOptions:(UIBarButtonItem *)barButton; +- (IBAction)sortList:(UISegmentedControl *)sortControl; @end +extern NSString * const kLETalkListSortOrder; extern NSString * const kLETalkListTalkSize; \ No newline at end of file diff --git a/Lemacs/LETalkListController.m b/Lemacs/LETalkListController.m index 1d826f8..f1c89d5 100644 --- a/Lemacs/LETalkListController.m +++ b/Lemacs/LETalkListController.m @@ -24,6 +24,7 @@ @interface LETalkListController () @property (nonatomic, strong, readonly) NSFetchedResultsController *fetchedResultsController; @property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext; +@property (nonatomic) BOOL reverseSort; @property (nonatomic) LETalkSize talkSize; - (IBAction)saveContext; @@ -36,7 +37,7 @@ @implementation LETalkListController + (void)initialize; { - [[NSUserDefaults standardUserDefaults] registerDefaults:@{kLETalkListTalkSize : @(kLETalkSizeRegular)}]; + [[NSUserDefaults standardUserDefaults] registerDefaults:@{kLETalkListTalkSize : @(kLETalkSizeRegular), kLETalkListSortOrder : @(NO)}]; } @@ -68,9 +69,16 @@ - (void)viewDidLoad; self.talkViewController = (LETalkViewController *)[[self.splitViewController.viewControllers lastObject] topViewController]; + self.reverseSort = [[NSUserDefaults standardUserDefaults] integerForKey:kLETalkListSortOrder]; self.talkSize = [[NSUserDefaults standardUserDefaults] integerForKey:kLETalkListTalkSize]; } +- (void)viewWillAppear:(BOOL)animated; +{ + [super viewWillAppear:animated]; + [self reloadList]; +} + - (void)didReceiveMemoryWarning; { [super didReceiveMemoryWarning]; @@ -103,6 +111,7 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; assert([segue.destinationViewController isKindOfClass:[LETalkViewController class]]); LETalkViewController *talkViewController = (LETalkViewController *)segue.destinationViewController; talkViewController.issue = issue; + talkViewController.navigationItem.prompt = issue.plainTitle; } else if ([[segue identifier] isEqualToString:@"SelectTalk"]) { assert([segue.destinationViewController isKindOfClass:[LEWorkViewController class]]); [[segue destinationViewController] setTalk:issue]; @@ -113,64 +122,12 @@ - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender; #pragma mark - NSFetchedResultsControllerDelegate -/* -- (void)controllerWillChangeContent:(NSFetchedResultsController *)controller; -{ - [self.tableView beginUpdates]; -} - -- (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type; -{ - switch(type) { - case NSFetchedResultsChangeInsert: - [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; - break; - - case NSFetchedResultsChangeDelete: - [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationFade]; - break; - } -} - -- (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath; -{ - UITableView *tableView = self.tableView; - - switch(type) { - case NSFetchedResultsChangeInsert: - [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; - break; - - case NSFetchedResultsChangeDelete: - [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; - break; - - case NSFetchedResultsChangeUpdate: - [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; - break; - - case NSFetchedResultsChangeMove: - [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade]; - [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; - break; - } -} - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { - [self.tableView endUpdates]; + [self.tableView reloadData]; } -*/ - // Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed. - - - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller - { - // In the simplest, most efficient, case, reload the table view. - [self.tableView reloadData]; - } -// */ - #pragma mark - UITableViewDataSource @@ -281,6 +238,8 @@ - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPa #pragma mark - API +#pragma mark Properties + @synthesize fetchedResultsController = _fetchedResultsController, managedObjectContext = _managedObjectContext; - (NSFetchedResultsController *)fetchedResultsController; @@ -291,37 +250,16 @@ - (NSFetchedResultsController *)fetchedResultsController; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; fetchRequest.entity = [NSEntityDescription entityForName:kGHIssueEntityName inManagedObjectContext:self.managedObjectContext]; fetchRequest.fetchBatchSize = 20; - fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:kGHCreatedDatePropertyName ascending:NO]]; + fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:kGHCreatedDatePropertyName ascending:self.reverseSort]]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil]; fetchedResultsController.delegate = self; - _fetchedResultsController = fetchedResultsController; - NSError *fetchError; - if ([fetchedResultsController performFetch:&fetchError]) { - if (!self.fetchedResultsController.sections.count) - [[GHStore sharedStore] loadIssues:YES]; - - return fetchedResultsController; // Success - } - - if (kLEUseNarrativeLogging) { - NSLog(@"Fetch Error: %@, %@", fetchError, fetchError.userInfo); - - NSString *whyThisHappened = @"abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development."; - NSLog(@"%@", whyThisHappened); - - NSString *whatShouldHappen = @"Replace this implementation with code to handle the error appropriately."; - NSLog(@"%@", whatShouldHappen); - } - - // TODO: Make this do what it's supposed to. + _fetchedResultsController = fetchedResultsController; - abort(); - - return nil; + return fetchedResultsController; } - (NSManagedObjectContext *)managedObjectContext; @@ -338,6 +276,29 @@ - (NSManagedObjectContext *)managedObjectContext; return _managedObjectContext; } +- (void)setReverseSort:(BOOL)reverseSort; +{ + if (_reverseSort == reverseSort) + return; + + _reverseSort = reverseSort; + [self reloadList]; + [[NSUserDefaults standardUserDefaults] integerForKey:kLETalkListSortOrder]; +} + +- (void)setTalkSize:(LETalkSize)talkSize; +{ + if (_talkSize == talkSize) + return; + + _talkSize = talkSize; + [self.tableView reloadData]; + [[NSUserDefaults standardUserDefaults] setInteger:talkSize forKey:kLETalkListTalkSize]; +} + + +#pragma mark Actions + - (IBAction)saveContext; { NSError *contextSavingError; @@ -365,7 +326,19 @@ - (IBAction)saveContext; - (IBAction)reloadList; { - [self.tableView reloadData]; + // If we switch from [^|v] to [v|^] change self.reverseSort to !self.reverseSort + self.fetchedResultsController.fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:kGHCreatedDatePropertyName ascending:self.reverseSort]]; + + NSError *fetchError; + if (![self.fetchedResultsController performFetch:&fetchError]) { + NSLog(@"Sort Error: %@", fetchError.userInfo); + return; + } + + if (self.fetchedResultsController.sections.count) + [self.tableView reloadData]; + else + [[GHStore sharedStore] loadIssues:YES]; } - (IBAction)resizeCells:(UIPinchGestureRecognizer *)pinch; @@ -375,7 +348,7 @@ - (IBAction)resizeCells:(UIPinchGestureRecognizer *)pinch; if (jumps) { self.talkSize = embiggens ? kLETalkSizeFull : kLETalkSizeFull; - [self reloadList]; + [self.tableView reloadData]; return; } @@ -398,14 +371,10 @@ - (IBAction)showOptions:(UIBarButtonItem *)barButton; NSLog(@"TODO: Implement %@ refs #%d", NSStringFromSelector(_cmd), 25); } -- (void)setTalkSize:(LETalkSize)talkSize; +- (IBAction)sortList:(UISegmentedControl *)sortControl; { - if (_talkSize == talkSize) - return; - - _talkSize = talkSize; - [self.tableView reloadData]; - [[NSUserDefaults standardUserDefaults] setInteger:talkSize forKey:kLETalkListTalkSize]; + if (self.reverseSort != sortControl.selectedSegmentIndex) + self.reverseSort = sortControl.selectedSegmentIndex; } - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath; @@ -431,4 +400,5 @@ - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPa @end +NSString * const kLETalkListSortOrder = @"LETalkList-ReverseSort"; NSString * const kLETalkListTalkSize = @"LETalkList-TalkSize"; diff --git a/Lemacs/LETalkViewController.h b/Lemacs/LETalkViewController.h index 43009c0..52045c9 100644 --- a/Lemacs/LETalkViewController.h +++ b/Lemacs/LETalkViewController.h @@ -12,4 +12,9 @@ @property (strong, nonatomic) GHIssue *issue; +- (IBAction)reloadList; +- (IBAction)sortList:(UISegmentedControl *)sortControl; + @end + +extern NSString * const kLETalkViewSortOrder; diff --git a/Lemacs/LETalkViewController.m b/Lemacs/LETalkViewController.m index 8f7fe9f..7afc43f 100644 --- a/Lemacs/LETalkViewController.m +++ b/Lemacs/LETalkViewController.m @@ -23,6 +23,7 @@ @interface LETalkViewController () @property (nonatomic, strong, readonly) NSFetchedResultsController *fetchedResultsController; @property (nonatomic, strong, readonly) NSManagedObjectContext *managedObjectContext; +@property (nonatomic) BOOL reverseSort; - (IBAction)insertNewObject; - (IBAction)saveContext; @@ -34,6 +35,11 @@ - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPa @implementation LETalkViewController ++ (void)initialize; +{ + [[NSUserDefaults standardUserDefaults] registerDefaults:@{kLETalkViewSortOrder : @(NO)}]; +} + #pragma mark - NSObject (UINibLoadingAdditions) - (void)awakeFromNib; @@ -53,6 +59,8 @@ - (void)viewDidLoad; [super viewDidLoad]; [self.tableView registerNib:[UINib nibWithNibName:NSStringFromClass([LETalkCell class]) bundle:nil] forCellReuseIdentifier:NSStringFromClass([LETalkCell class])]; + + self.reverseSort = [[NSUserDefaults standardUserDefaults] integerForKey:kLETalkViewSortOrder]; } - (void)didReceiveMemoryWarning; @@ -126,17 +134,6 @@ - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller [self.tableView endUpdates]; } -/* - // Implementing the above methods to update the table view in response to individual changes may have performance implications if a large number of changes are made simultaneously. If this proves to be an issue, you can instead just implement controllerDidChangeContent: which notifies the delegate that all section and object changes have been processed. - - - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller - { - // In the simplest, most efficient, case, reload the table view. - [self.tableView reloadData]; - } - */ - - #pragma mark - UITableViewDataSource @@ -224,33 +221,16 @@ - (NSFetchedResultsController *)fetchedResultsController; fetchRequest.entity = [NSEntityDescription entityForName:kGHCommentEntityName inManagedObjectContext:self.managedObjectContext]; fetchRequest.fetchBatchSize = 20; fetchRequest.predicate = issuePredicate; - fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:kGHCreatedDatePropertyName ascending:NO]]; + fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:kGHCreatedDatePropertyName ascending:self.reverseSort]]; // Edit the section name key path and cache name if appropriate. // nil for section name key path means "no sections". NSFetchedResultsController *fetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:self.managedObjectContext sectionNameKeyPath:nil cacheName:nil]; fetchedResultsController.delegate = self; - _fetchedResultsController = fetchedResultsController; - NSError *fetchError = nil; - if ([fetchedResultsController performFetch:&fetchError]) - return fetchedResultsController; // Success - - if (kLEUseNarrativeLogging) { - NSLog(@"Fetch Error: %@, %@", fetchError, fetchError.userInfo); - - NSString *whyThisHappened = @"abort() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development."; - NSLog(@"%@", whyThisHappened); - - NSString *whatShouldHappen = @"Replace this implementation with code to handle the error appropriately."; - NSLog(@"%@", whatShouldHappen); - } - - // TODO: Make this do what it's supposed to. - - abort(); + _fetchedResultsController = fetchedResultsController; - return nil; + return fetchedResultsController; } - (NSManagedObjectContext *)managedObjectContext; @@ -258,6 +238,16 @@ - (NSManagedObjectContext *)managedObjectContext; return self.issue.managedObjectContext; } +- (void)setReverseSort:(BOOL)reverseSort; +{ + if (_reverseSort == reverseSort) + return; + + _reverseSort = reverseSort; + [self reloadList]; + [[NSUserDefaults standardUserDefaults] integerForKey:kLETalkViewSortOrder]; +} + - (IBAction)insertNewObject; { NSManagedObject *newManagedObject = [NSEntityDescription insertNewObjectForEntityForName:self.fetchedResultsController.fetchRequest.entity.name inManagedObjectContext:self.fetchedResultsController.managedObjectContext]; @@ -270,6 +260,23 @@ - (IBAction)insertNewObject; [self saveContext]; } +- (IBAction)reloadList; +{ + // If we switch from [^|v] to [v|^] change self.reverseSort to !self.reverseSort + self.fetchedResultsController.fetchRequest.sortDescriptors = @[[[NSSortDescriptor alloc] initWithKey:kGHCreatedDatePropertyName ascending:self.reverseSort]]; + + NSError *fetchError; + if (![self.fetchedResultsController performFetch:&fetchError]) { + NSLog(@"Sort Error: %@", fetchError.userInfo); + return; + } + + if (self.fetchedResultsController.sections.count) + [self.tableView reloadData]; + else + [[GHStore sharedStore] loadIssues:YES]; +} + - (IBAction)saveContext; { NSError *contextSavingError; @@ -295,6 +302,12 @@ - (IBAction)saveContext; abort(); } +- (IBAction)sortList:(UISegmentedControl *)sortControl; +{ + if (self.reverseSort != sortControl.selectedSegmentIndex) + self.reverseSort = sortControl.selectedSegmentIndex; +} + - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { assert([cell isKindOfClass:[LETalkCell class]]); @@ -309,3 +322,4 @@ - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPa @end +NSString * const kLETalkViewSortOrder = @"LETalkView-ReverseSort"; diff --git a/Lemacs/LEWorkViewController.m b/Lemacs/LEWorkViewController.m index 742f981..6f1d13c 100644 --- a/Lemacs/LEWorkViewController.m +++ b/Lemacs/LEWorkViewController.m @@ -191,6 +191,7 @@ - (void)configureView; // Default to editing mode if this is an uncommited talk self.editing = IsEmpty([(NSObject *)self.talk valueForKey:kLETalkBodyKey]) || self.talk.hasChanges; + self.navigationItem.prompt = self.talk.plainTitle; self.segmentedControl.selectedSegmentIndex = self.editing ? 1 : 0; }