Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

UITableView deleteRowsAtIndexPath crash when delete last record

I'm encountering the following error when I delete the last record from a UITableView.

Terminating app due to uncaught exception 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of rows in section 0. The number of rows contained in an existing section after the update (3) 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, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

My goal is to show "No Record found" if the table array is empty.

This is the code I'm using. When I delete the last record from table array the app crashes. How is it possible to reload the table and show "No Record Found" label?

// Customize the number of rows in the table view.
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
    if ([idArray count]==0) {
        return  3;
    }
    else 
    {
        return [idArray count];
    }   
}

// Customize the appearance of table view cells.
- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {

    NSLog(@"array count %d",[idArray count]);
    if ([idArray count] == 0) {
        static NSString *CellIdentifier = @"Cell";

        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:CellIdentifier];
        }
        cell.textLabel.textAlignment = UITextAlignmentCenter;

        tableView.userInteractionEnabled = NO;

        self.navigationItem.leftBarButtonItem.enabled = NO;
         NSUInteger row = [indexPath row];

        switch (row) {
            case 0:
                cell.textLabel.text = @"";
                break;
            case 1:
                cell.textLabel.text = @"";
                break;

            case 2:
                cell.textLabel.text = @"No Records Found";
                break;


            default:
                break;
        }        

        return cell;
    }
    else
    {   static NSString *CellIdentifier = @"Cell";

        UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
        if (cell == nil) {
            cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:CellIdentifier];
        }

        tableView.userInteractionEnabled = YES;

        self.navigationItem.leftBarButtonItem.enabled = YES;

        // Set up the cell
        identify *idItems = [idArray objectAtIndex:indexPath.row];

        NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
        [formatter setDateFormat:@"dd MMM,yyyy"];
        NSString *dateStr = [formatter stringFromDate:idItems.Date];
        UIImageView *accDis = [[UIImageView alloc] initWithImage:[UIImage imageNamed:@"Arrow.png"]];    
        cell.accessoryView = accDis;
        self.idTableView.separatorColor = [UIColor colorWithRed:150.0/255.0 green:150.0/255.0 blue:150.0/255.0 alpha:1];
        cell.textLabel.textColor = [UIColor blackColor];
        cell.textLabel.font = [UIFont boldSystemFontOfSize:18];
        cell.textLabel.adjustsFontSizeToFitWidth = YES;
        cell.detailTextLabel.textColor = [UIColor colorWithRed:100.0/255.0 green:100.0/255.0 blue:100.0/255.0 alpha:1];
        cell.detailTextLabel.font = [UIFont italicSystemFontOfSize:16];
        cell.detailTextLabel.adjustsFontSizeToFitWidth = YES;

        NSString *detailText = [NSString stringWithFormat:@"%@ - %@",dateStr,idItems.GeoCode];

        if (idItems.Image == NULL) {
            cell.imageView.image = [UIImage imageNamed:@"icon58x58.png"];
        }
        else
        {
        //pass image to fix size 50 X 50
        //UIImage *newImage = [self postProcessImage:idItems.Image];
            cell.imageView.image = idItems.thumb;//newImage; 
        cell.imageView.contentMode=UIViewContentModeScaleAspectFill;
        }

        cell.textLabel.text = idItems.TypeName; 
        cell.detailTextLabel.text = detailText;

        return cell;   
    }    
}

- (void)tableView:(UITableView *)tv commitEditingStyle:(UITableViewCellEditingStyle)editingStyle 
    forRowAtIndexPath:(NSIndexPath *)indexPath {

    if(editingStyle == UITableViewCellEditingStyleDelete) {

        if ([idArray count] >=1) 
        {
            [idTableView beginUpdates];

            //Get the object to delete from the array.
            identifyObject = [appDelegate.idArray objectAtIndex:indexPath.row];

            //Delete the object from the table.
            [self.idTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
             [appDelegate removeID:identifyObject];

            if ([idArray count] == 0) {
                [self.idTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
            }
            [idTableView endUpdates];
        }
    }
}
like image 375
Desmond Avatar asked Jan 22 '12 08:01

Desmond


3 Answers

The problem is that a tableview expects the operations performed on the view to match the data source. You have one record in the table, and you remove it. The tableview is expecting the datasource to now contain zero records, but because of your "no records found" logic, it actually returns a value of 3, hence the consistency error, and your crash.

The bug seems to be this part:

if ([idArray count] == 0) {
    [self.idTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}

I assume this was intended to insert the "no records found" row into the table when the last line is deleted, but since your "no records found" actually spans three rows, you need to insert three rows here instead, like this:

if ([idArray count] == 0) {
    [self.idTableView insertRowsAtIndexPaths:[NSArray arrayWithObjects:
    [NSIndexPath indexPathForRow:0 inSection:indexPath.section],
    [NSIndexPath indexPathForRow:1 inSection:indexPath.section],
    [NSIndexPath indexPathForRow:2 inSection:indexPath.section],
    nil] withRowAnimation:UITableViewRowAnimationFade];
}

For you own sanity however, can I suggest a different approach? Rather than trying to keep your table and datasource in sync whilst juggling these fake three rows of data that are only there for display purposes, why not just insert a UILabel into your view hierarchy (either in front of or behind the tableview) that says "no records found" and show/hide it based on whether the table has any data? That way you can precisely control its position and appearance without having to screw around with your datasource logic.

like image 126
Nick Lockwood Avatar answered Nov 20 '22 03:11

Nick Lockwood


General rules for dealing with deleting rows are:

  1. Deal with your model
  2. Deal with row's animation

So for example:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

   if (editingStyle == UITableViewCellEditingStyleDelete) {
       NSInteger row = [indexPath row];

       [yourModel removeObjectAtIndex:row]; // you need to update your model

       [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]    withRowAnimation:UITableViewRowAnimationFade];
    }
}

Now, in my opinion the correct code could be the following (I've written some comments to guide you).

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

    if (editingStyle == UITableViewCellEditingStyleDelete) {
        //Get the object to delete from the array.
        identifyObject = [appDelegate.idArray objectAtIndex:indexPath.row];
        [appDelegate removeID:identifyObject]; // update model first

        // now you can check model count and do what you want
        if ([appDelegate.idArray count] == 0) // I think you mean appDelegate.idArray
        {   
            // do what you want
            // with  [self.idTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        }

        else
        {
            [self.idTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
        }
    }
}

Hope it helps.

like image 35
Lorenzo B Avatar answered Nov 20 '22 03:11

Lorenzo B


I was using same approach where I used a cell for "No rows" warning.

For me, this worked:

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {

if (editingStyle == UITableViewCellEditingStyleDelete) {

    [favs removeObjectAtIndex:indexPath.section];

    if ([favs count] == 0) {
        [tableView reloadRowsAtIndexPaths:[[NSArray alloc]initWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
        [tableView setEditing:NO animated:YES];

        // Remove Edit bar button item
        self.navigationItem.rightBarButtonItem = nil;
    }
    else {
        // Animate the deletion from the table.
        [tableView deleteRowsAtIndexPaths:[[NSArray alloc]initWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationFade];
    }
}

}

like image 2
Breeno Avatar answered Nov 20 '22 02:11

Breeno