I'm trying to place a custom empty cell when my UITableView is empty.
I use the following code:
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
int count = [self.fetchedResultsController fetchedObjects].count;
if (count == 0) {
return 1;
}
return count;
}
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
UITableViewCell * cell = nil;
int count = [self.fetchedResultsController fetchedObjects].count;
if (count == 0) {
// return empty cell
cell = [self getEmptyCellOfTableView:tableView cellForRowAtIndexPath:indexPath];
} else {
cell = [self getMyQuestionCellOfTableView:tableView cellForRowAtIndexPath:indexPath];
}
return cell;
}
FRC didChangeObject implementation:
- (void)controller:(NSFetchedResultsController *)controller
didChangeObject:(id)anObject
atIndexPath:(NSIndexPath *)indexPath
forChangeType:(NSFetchedResultsChangeType)type
newIndexPath:(NSIndexPath *)newIndexPath
{
if (!self.suspendAutomaticTrackingOfChangesInManagedObjectContext)
{
switch(type)
{
case NSFetchedResultsChangeInsert:
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeDelete:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeUpdate:
[self.tableView reloadRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
case NSFetchedResultsChangeMove:
[self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
}
}
}
This works perfectly, the problam is when the fetchedObject.count > 0 than i get the following error:
CoreData: error: Serious application error.
An exception was caught from the delegate of NSFetchedResultsController during a call to -
controllerDidChangeContent: Invalid update: invalid number of rows in section 0.
The number of rows contained in an existing section after the update (1)
must be equal to the number of rows contained in that section before the update (1),
plus or minus the number of rows inserted or deleted from that section (1 inserted, 0 deleted)
and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).
with userInfo (null)
I understand that this happens because I inserted 1 new row and the count stays at 1 instead of becoming 2.
How can i fix it to fit my behavior?
After a table view update, the value returned by numberOfRowsInSection
must exactly
match the previous number of rows plus the number of inserted rows minus the number of
deleted rows.
In your case, when the first object is inserted, insertRowsAtIndexPaths
is called in the FRC delegate method for the new object, but instead of displaying an additional row, the "empty"
cell is replaced with a different cell, so the number of rows is still one.
It should work if you modify the FRC delegate method
controller:didChangeObject:atIndexPath:forChangeType:newIndexPath:
as follows:
case NSFetchedResultsChangeInsert:
if ([[self.fetchedResultsController fetchedObjects] count] == 1) {
// First object inserted, "empty cell" is replaced by "object cell"
[tableView reloadRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
} else {
[tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
}
break;
case NSFetchedResultsChangeDelete:
if ([[self.fetchedResultsController fetchedObjects] count] == 0) {
// Last object removed, "object cell" is replaced by "empty cell"
[tableView reloadRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
} else {
[tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
break;
PSA: If you haven't checked out Yang Meyer's solution yet for displaying a cell on a empty table, I suggest you do. It's clever and a much simpler way of handling an empty table or empty NSFetchedResultsController.
Yang Meyer's Best Practice for Handling Empty Table Views
The way that I used it was I would in my viewDidLoad
, I would initially assume that the results were empty. Only when my fetchedResultsController
had objects, do I change the data source back.
FJFetchedDataController.h
@interface FJFetchedDataTableController : UITableViewController <NSFetchedResultsControllerDelegate, UITableViewDelegate>
@end
FJFetchedDataController.m
@implementation FJFetchedDataTableController {
@private
FJEmptyDataSource *_emptyDataSource;
};
- (void)viewDidLoad {
[super viewDidLoad];
// create the empty data source
_emptyDataSource = [[FJEmptyDataSource alloc] init];
// point the tableView to the empty data source
self.tableView.dataSource = _emptyDataSource;
self.fetchedResultsController.delegate = self;
// TODO: configure your fetchedResultsController here
NSError *error;
if (![self.fetchedResultsController performFetch:&error]) {
// TODO: error handling
}
if ([self.fetchedResultsController.fetchedObjects count]) {
// set the table source back to itself (or whatever you want as the data source)
// since we know we have results
self.tableView.dataSource = self;
return;
}
}
Simple, yet really effective.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With