Note that there are some explanatory texts on larger screens.

plurals
  1. POSubclassing UITableviewController to make template for Core Data
    primarykey
    data
    text
    <p>I'm refactoring an application that's already in the app store. </p> <p>In this app, I have a UITabBar with two tabs. Each tab contains a tableview with a list of Customers and Inventory respectively. The tableview is backed by core data and I have methods in place to swipe the table view to change the sorting of the data.</p> <p>These two controllers are just the same bar the names of the entities I pass into the NSFetchedResultsController. My aim was to create a super class that would be a template of these two views (as we re-use these views in other apps) so we can rapidly prototype future projects.</p> <p>However, my attempts have failed. I know the problem lies in the NSFetchedResultsController, but I just don't know why. When I try to run the app it fires the error </p> <pre><code>Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil' </code></pre> <p>Here's my code for the super class and one of the view controllers. If anybody has any insights I'd be really grateful. </p> <p>FocusTableViewController.h</p> <pre><code>#import &lt;UIKit/UIKit.h&gt; @interface FocusTableViewController : UITableViewController&lt;NSFetchedResultsControllerDelegate, UISearchBarDelegate, UISearchDisplayDelegate, UITableViewDelegate, UITableViewDataSource&gt; { @protected //Remembering where the tableview was when you click through on an item CGPoint savedScrollPosition; //Our Shit for sorting NSString *dEntity; NSString *dCacheName; NSString *dSectionKey0; NSString *dSectionKey1; NSString *dSortKey0; NSString *dSortKey1; //The Current sorting criteria //These have to be mutable, so they can change NSMutableString *currentKey; NSMutableString *currentSectionKey; //Filter Predicate For Searching NSString *dFilterPredicate; } @property (nonatomic) BOOL searchIsActive; /** DAO **/ @property (nonatomic, strong) NSFetchedResultsController *fetchedResultsController; @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; @property (nonatomic, strong) UISearchDisplayController *searchDisplayController; /** Custom Constructor **/ - (id)initWithTitle:(NSString *)title; /** Search Bar **/ - (void)setupSearchBar; - (void)makeSearchBarActive:(id)sender; /** Sorting Methods **/ - (void)sortTableView; - (void)changeFetchData; @end </code></pre> <p>FocusTableViewController.m</p> <pre><code>#import "FocusTableViewController.h" #import "AppDelegate.h" @interface FocusTableViewController () - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath; @end @implementation FocusTableViewController @synthesize fetchedResultsController = __fetchedResultsController; @synthesize managedObjectContext = __managedObjectContext; @synthesize searchDisplayController = __searchDisplayController; @synthesize searchIsActive; - (id)initWithTitle:(NSString *)title { self = [super initWithStyle:UITableViewStylePlain]; if (self) { self.title = title; [self setupSearchBar]; self.tableView.dataSource = self; self.tableView.delegate = self; } return self; } - (id)initWithStyle:(UITableViewStyle)style { self = [super initWithStyle:style]; if (self) { } return self; } - (void)viewDidLoad { [super viewDidLoad]; if (__managedObjectContext == nil) { __managedObjectContext = [(AppDelegate *)[[UIApplication sharedApplication] delegate] managedObjectContext]; } self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemSearch target:self action:@selector(makeSearchBarActive:)]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (savedScrollPosition.y == CGPointZero.y) { [self.tableView setContentOffset:CGPointMake(0, 44.f) animated:NO]; } else { [self.tableView setContentOffset:savedScrollPosition animated:NO]; } } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. // e.g. self.myOutlet = nil; } - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { return YES; } #pragma mark - Table view data source - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { return [[self.fetchedResultsController sections] count]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { id &lt;NSFetchedResultsSectionInfo&gt; sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo numberOfObjects]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { static NSString *CellIdentifier = @"Cell"; UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier]; if (cell == nil) { cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier]; } [self configureCell:cell atIndexPath:indexPath]; return cell; } - (void)configureCell:(UITableViewCell *)cell atIndexPath:(NSIndexPath *)indexPath { NSManagedObject *managedObject = [self.fetchedResultsController objectAtIndexPath:indexPath]; if ([currentKey isEqualToString:dSortKey0]) { cell.textLabel.text = [[managedObject valueForKey:@"title"] description]; } else { cell.textLabel.text = [NSString stringWithFormat:@"%@ - %@",[[managedObject valueForKey:@"accountno"] description],[[managedObject valueForKey:@"title"] description]]; } } // Support rearranging the table view. - (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath { } // Support conditional rearranging of the table view. - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } #pragma mark - Table View Customisation // Creates the scrubber on the right hand side of the list - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { if (searchIsActive) { return [__fetchedResultsController sectionIndexTitles]; } else { return [[NSArray arrayWithObject:@"{search}"]arrayByAddingObjectsFromArray:[__fetchedResultsController sectionIndexTitles]]; } } - (NSInteger) tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { if (index == 0) { [tableView setContentOffset:CGPointZero animated:NO]; return NSNotFound; } return index; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { if (!(section == 0 &amp;&amp; [self.tableView numberOfSections] == 1)) { id &lt;NSFetchedResultsSectionInfo&gt; sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; return [sectionInfo name]; } return nil; } #pragma mark - Table view delegate #pragma mark - Search Bar - (void)setupSearchBar { UISearchBar *mySearchBar = [[UISearchBar alloc] init]; mySearchBar.delegate = self; [mySearchBar setAutocapitalizationType:UITextAutocapitalizationTypeNone]; [mySearchBar sizeToFit]; self.tableView.tableHeaderView = mySearchBar; __searchDisplayController = [[UISearchDisplayController alloc] initWithSearchBar:mySearchBar contentsController:self]; [self setSearchDisplayController:__searchDisplayController]; [__searchDisplayController setDelegate:self]; [__searchDisplayController setSearchResultsDataSource:self]; } - (void)makeSearchBarActive:(id)sender { [__searchDisplayController setActive:YES animated:YES]; } #pragma mark - #pragma mark Content Filtering - (void)filterContentForSearchText:(NSString*)searchText scope:(NSString*)scope { [NSFetchedResultsController deleteCacheWithName:dCacheName]; NSFetchRequest *aRequest = [[self fetchedResultsController] fetchRequest]; NSPredicate *predicate = [NSPredicate predicateWithFormat:dFilterPredicate, searchText]; [aRequest setPredicate:predicate]; NSError *error = nil; if (![[self fetchedResultsController] performFetch:&amp;error]) { //_ERROR_ Save Error } } #pragma mark - #pragma mark UISearchDisplayController Delegate Methods - (void)searchDisplayControllerWillBeginSearch:(UISearchDisplayController *)controller { [self setSearchIsActive:YES]; } - (void)searchDisplayControllerDidEndSearch:(UISearchDisplayController *)controller { [NSFetchedResultsController deleteCacheWithName:dCacheName]; self.fetchedResultsController = nil; //For some reason the iPad version can't calculate whether there's a navbar or not //see : http://stackoverflow.com/questions/6591674/uisearchdisplaycontroller-gray-overlay-not-fully-covering-table if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) { [self.tableView setContentOffset:CGPointMake(0, 44.f) animated:NO]; } } - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchString:(NSString *)searchString { [self filterContentForSearchText:searchString scope:[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:[self.searchDisplayController.searchBar selectedScopeButtonIndex]]]; // Return YES to cause the search result table view to be reloaded. return YES; } - (BOOL)searchDisplayController:(UISearchDisplayController *)controller shouldReloadTableForSearchScope:(NSInteger)searchOption { [self filterContentForSearchText:[self.searchDisplayController.searchBar text] scope:[[self.searchDisplayController.searchBar scopeButtonTitles] objectAtIndex:searchOption]]; // Return YES to cause the search result table view to be reloaded. return YES; } - (void)searchDisplayControllerDidBeginSearch:(UISearchDisplayController *)controller { /* Because the searchResultsTableView will be released and allocated automatically, so each time we start to begin search, we set its delegate here. */ [self.searchDisplayController.searchResultsTableView setDelegate:self]; } #pragma mark - Sort Table View - (void)changeFetchData { self.fetchedResultsController.delegate = nil; __fetchedResultsController = nil; NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; NSEntityDescription *entity = [NSEntityDescription entityForName:dEntity inManagedObjectContext:__managedObjectContext]; [fetchRequest setEntity:entity]; NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:currentKey ascending:YES selector:@selector(caseInsensitiveCompare:)]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:__managedObjectContext sectionNameKeyPath:currentSectionKey cacheName:nil]; self.fetchedResultsController = aFetchedResultsController; __fetchedResultsController.delegate = self; NSError *error; if (![[self fetchedResultsController] performFetch:&amp;error]) { //_ERROR_ // Update to handle the error appropriately. NSLog(@"Fetch failed"); NSLog(@"Unresolved error %@, %@", error, [error userInfo]); exit(-1); // Fail } [self.tableView reloadData]; } - (void)sortTableView { if ([currentKey isEqualToString:dSortKey0]) { [currentKey setString:dSortKey1]; [currentSectionKey setString:dSectionKey1]; } else if ([currentKey isEqualToString:dSortKey1]) { [currentKey setString:dSortKey0]; [currentSectionKey setString:dSectionKey0]; } [NSFetchedResultsController deleteCacheWithName:dCacheName]; [self.tableView reloadData]; [self changeFetchData]; } #pragma mark - Fetched results controller - (NSFetchedResultsController *)fetchedResultsController { if (__fetchedResultsController != nil) { return __fetchedResultsController; } // Create the fetch request for the entity. NSFetchRequest *fetchRequest = [[NSFetchRequest alloc] init]; // Configure the Entity NSEntityDescription *entity = [NSEntityDescription entityForName:dEntity inManagedObjectContext:__managedObjectContext]; [fetchRequest setEntity:entity]; [fetchRequest setFetchBatchSize:20]; NSPredicate *predicate = [NSPredicate predicateWithFormat:@"deleted == 0"]; [fetchRequest setPredicate:predicate]; //Configure Sort Descriptors NSSortDescriptor *sortDescriptor = [[NSSortDescriptor alloc] initWithKey:currentKey ascending:YES selector:@selector(caseInsensitiveCompare:)]; NSArray *sortDescriptors = [[NSArray alloc] initWithObjects:sortDescriptor, nil]; [fetchRequest setSortDescriptors:sortDescriptors]; [fetchRequest setReturnsObjectsAsFaults:NO]; // nil for section name key path means "no sections". NSFetchedResultsController *aFetchedResultsController = [[NSFetchedResultsController alloc] initWithFetchRequest:fetchRequest managedObjectContext:__managedObjectContext sectionNameKeyPath:currentSectionKey cacheName:dCacheName]; aFetchedResultsController.delegate = self; self.fetchedResultsController = aFetchedResultsController; NSError *error = nil; if (![self.fetchedResultsController performFetch:&amp;error]) { //_ERROR_ NSLog(@"The error is %@",error); } return __fetchedResultsController; } #pragma mark - Fetched results controller delegate - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { if ([self searchIsActive]) { [[[self searchDisplayController] searchResultsTableView] beginUpdates]; } else { [self.tableView beginUpdates]; } } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id &lt;NSFetchedResultsSectionInfo&gt;)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: [NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeDelete: [tableView deleteRowsAtIndexPaths: [NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; break; case NSFetchedResultsChangeUpdate: [self configureCell:[tableView cellForRowAtIndexPath:indexPath] atIndexPath:indexPath]; break; case NSFetchedResultsChangeMove: [tableView deleteRowsAtIndexPaths: [NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade]; [tableView insertRowsAtIndexPaths: [NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade]; break; } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { if ([self searchIsActive]) { [[[self searchDisplayController] searchResultsTableView] endUpdates]; } else { [self.tableView reloadData]; [self.tableView endUpdates]; } } @end </code></pre> <p>The CustomerViewController_iPhone.h is blank and here's the .m</p> <pre><code>#import "CustomerViewController_iPhone.h" #import "CustomerDetailViewController_iPhone.h" @interface CustomerViewController_iPhone () @end @implementation CustomerViewController_iPhone - (id)initWithTitle:(NSString *)title { self = [super initWithTitle:title]; if (self) { dEntity = @"Customer"; dCacheName = @"Customer_Cache"; dSectionKey0 = @"nameFirstLetter"; dSectionKey1 = @"accountNoFirstNumber"; dSortKey0 = @"title"; dSortKey1 = @"accountno"; dFilterPredicate = @"(title contains[cd] %@ OR accountno contains[cd] %@) AND deleted == 0"; } return self; } - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view. currentKey = [dSortKey0 mutableCopy]; currentSectionKey = [dSectionKey0 mutableCopy]; } - (void)viewDidUnload { [super viewDidUnload]; // Release any retained subviews of the main view. } -(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { savedScrollPosition = [tableView contentOffset]; CustomerDetailViewController_iPhone *detail = [[CustomerDetailViewController_iPhone alloc] initWithNibName:@"CustomerDetailViewController_iPhone" bundle:nil]; [self.navigationController pushViewController:detail animated:YES]; } @end </code></pre> <h1>EDIT</h1> <p>I can't answer my own question yet for another 3 hours because I don't have enough reputation. Here's what I wrote:</p> <p>I've found the problem. When I instantiate the view controller, the first time the NSFetchedResultsController is being called, the variables requires are null. Stupid I know. Maybe, if I had of posted this snippet of code earlier, someone may have picked up on the answer.</p> <pre><code>/** Set up the Customers TableView **/ NSString *customerTitle = NSLocalizedString(@"Customers", @"The user's list of customers"); CustomerViewController_iPhone *customers = [[CustomerViewController_iPhone alloc] initWithTitle:customerTitle]; UINavigationController *customers_nav = [[UINavigationController alloc] initWithRootViewController:customers]; </code></pre> <p>Thanks to Nikita and Phillip for the help. </p>
    singulars
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    plurals
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
    1. This table or related slice is empty.
 

Querying!

 
Guidance

SQuiL has stopped working due to an internal error.

If you are curious you may find further information in the browser console, which is accessible through the devtools (F12).

Reload