我正在重构已经在应用商店中的应用程序。
在这个应用程序中,我有一个带有两个选项卡的UITabBar。每个选项卡都包含一个tableview,其中包含Customers和Inventory列表。 tableview由核心数据支持,我有适当的方法来滑动表视图以更改数据的排序。
这两个控制器与我传递给NSFetchedResultsController的实体的名称相同。我的目标是创建一个超类,它将成为这两个视图的模板(因为我们在其他应用程序中重用这些视图),因此我们可以快速构建未来项目的原型。
然而,我的尝试失败了。我知道问题在于NSFetchedResultsController,但我只是不知道为什么。当我尝试运行应用程序时会触发错误
Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '*** -[__NSArrayM insertObject:atIndex:]: object cannot be nil'
这是我的超类和一个视图控制器的代码。如果有人有任何见解,我会非常感激。
FocusTableViewController.h
#import <UIKit/UIKit.h>
@interface FocusTableViewController : UITableViewController<NSFetchedResultsControllerDelegate, UISearchBarDelegate, UISearchDisplayDelegate, UITableViewDelegate, UITableViewDataSource>
{
@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
FocusTableViewController.m
#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 <NSFetchedResultsSectionInfo> 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 && [self.tableView numberOfSections] == 1))
{
id <NSFetchedResultsSectionInfo> 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:&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:&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:&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 <NSFetchedResultsSectionInfo>)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
CustomerViewController_iPhone.h为空白,这是.m
#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
我再过3个小时无法回答我自己的问题,因为我没有足够的声誉。这是我写的:
我发现了问题。当我实例化视图控制器时,第一次调用NSFetchedResultsController时,变量require为null。愚蠢我知道。也许,如果我之前发布了这段代码,有人可能已经接受了答案。
/** 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];
感谢Nikita和Phillip的帮助。