Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error 'Invalid update: invalid number of rows in section 0' attempting to delete row in table

My code appears to run just fine but when I swipe to delete a line within my UITableView, the app crashes with the following:

Error

LittleToDoApp[70390:4116002] *** 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 (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 (0 inserted, 1 deleted) and plus or minus the number of rows moved into or out of that section (0 moved in, 0 moved out).'

ViewController.m

#import "ViewController.h"
#import "ToDoItem.h"
#import "ToDoItemSvcCache.h"

@interface ViewController ()

@end

@implementation ViewController

@synthesize tableView;

ToDoItemSvcCache *ToDoItemSvc = nil;

- (void)viewDidLoad
{
    [super viewDidLoad];
    // Do any additional setup after loading the view, typically from a nib.

    ToDoItemSvc = [[ToDoItemSvcCache alloc] init];
}



- (void)didReceiveMemoryWarning
{
    [super didReceiveMemoryWarning];
    // Dispose of any resources that can be recreated.
}

- (IBAction)deleteToDoItem:(id)sender {
    NSLog(@"Deleting ToDoItem");

    [self.view endEditing:YES];

}

- (IBAction)addToDoItem:(id)sender {

    [self.view endEditing:YES];

    NSLog(@"saveToDoItem: entering");
    ToDoItem *todoitem = [[ToDoItem alloc] init];
    todoitem.todoitem = _toDoItem.text;
    [ToDoItemSvc createToDoItem:todoitem];

    [self.tableView reloadData];
    NSLog(@"saveToDoItem: todoitem saved");

}


- (UITableViewCell *)tableView:(UITableView *)tableView
         cellForRowAtIndexPath:(NSIndexPath *)indexPath {
    static NSString *simpleTableIdentifier = @"toDoItemCell";
    UITableViewCell *cell =
    [tableView dequeueReusableCellWithIdentifier:simpleTableIdentifier];
    if (cell == nil) {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault
                                  reuseIdentifier:simpleTableIdentifier];
    }
    ToDoItem *toDoItem = [[ToDoItemSvc retrieveAllToDoItems]
                    objectAtIndex:indexPath.row];
    cell.textLabel.text = [toDoItem description];
    return cell;
}



- (NSInteger)tableView:(UITableView *)tableView
 numberOfRowsInSection:(NSInteger)section
{
    return [[ToDoItemSvc retrieveAllToDoItems] count];
}

- (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
{
    return 1;
}



- (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
    if ([segue.identifier isEqualToString:@"viewToDoItem"]) {
        NSIndexPath *indexPath = [self.tableView indexPathForSelectedRow];
        SecondViewController *destViewController = segue.destinationViewController;
        UITableViewCell *cell = [tableView cellForRowAtIndexPath:indexPath];
        destViewController.toDoItemName = cell.textLabel.text;
    }
}

#pragma hiding status bar

- (BOOL)prefersStatusBarHidden {
    return YES;
}

// here we get back from both styles
- (IBAction)unwindFromDetailViewController:(UIStoryboardSegue *)segue
{
    // UIViewController *detailViewController = [segue sourceViewController];
    NSLog(@"%@", segue.identifier);
}

//Allows the delete button to show up when left swipping a list item

- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Return YES - we will be able to delete all rows
    return YES;
}

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
    // Will add code to actually delete a row here. Adding NSLog so we know its triggering though
    NSLog(@"Deleted row.");

    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

    [self.tableView reloadData];

}

@end

ToDoItemSvc.h

#import <Foundation/Foundation.h>
#import "ToDoItem.h"

@protocol ToDoItemSvc <NSObject>

    - (ToDoItem *) createToDoItem: (ToDoItem *) todoitem;
    - (NSMutableArray *) retrieveAllToDoItems;
    - (ToDoItem *) updateToDoItem: (ToDoItem *) todoitem;
    - (ToDoItem *) deleteToDoItem: (ToDoItem *) todoitem;

@end

Full source

https://github.com/martylavender/LittleToDoApp/tree/Storyboards

Edit

Following up after the comment/s made by Fennelouski, should I have something along these lines?

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

    if (editingStyle == UITableViewCellEditingStyleDelete) {
        [self.toDoItem removeObjectAtIndex:indexPath.row];

        [tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationLeft];
        [self.tableView reloadData];

    }
}

Edit 2

This is what I am getting:

https://www.evernote.com/l/AJiah58lVhdGXIYO1F5yv6fJXc7k3WjRLNYB/image.png

like image 353
Marty Avatar asked May 28 '15 20:05

Marty


2 Answers

The number of rows in your table is [[ToDoItemSvc retrieveAllToDoItems] count]. When you delete 1 row in your table, then the number of rows in your table should be 1 less than the number of rows before deleting any rows. After you delete 1 row and call [self.tableView reloadData] the tableView checks to see how many rows there are in the table. At this point, numberOfRowsInSection will return [[ToDoItemSvc retrieveAllToDoItems] count]. This should now be 1 less than it was before you deleted a row.

The short answer is, you need to first remove an item from your dataSource, which appears to be [ToDoItemSvc retrieveAllToDoItems] then delete a row.

The compliment to this is when you add a row, you need to add an item to your dataSource as well.

These changes need to happen before you call reloadData.

Edit

- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
    // Actually remove the data from the source
    [ToDoItemSvc deleteToDoItem:[ToDoItemSvc retrieveAllToDoItems][indexPath.row]]

    [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];

    [self.tableView reloadData];
}

ELI5: A teacher has five students: Alice, Bob, Charlie, Diane, and Eric. Bob's mom picks him up early from school before lunch. After lunch, the teacher takes attendance and panics because he only has four kids when the list says there should be five. Where's Bob?!

If Bob's mom had removed his name from the list when she took him out of school then the teacher wouldn't have panicked.

like image 118
Fennelouski Avatar answered Oct 24 '22 12:10

Fennelouski


I figured it out with the help from above and some thinking.

First, I finished the actual deleteToDoItem code

- (ToDoItem *) deleteToDoItem: (ToDoItem *) todoitem {

    [ToDoItems removeObject:todoitem];

    return todoitem;
}

Then the code above

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

    ToDoItem *toDoItem = [[ToDoItemSvc retrieveAllToDoItems] objectAtIndex:indexPath.row];

    [ToDoItemSvc deleteToDoItem:toDoItem];
    [self.tableView reloadData];

    NSLog(@"Removing data");
}

This runs and allows me to delete my item like I want!!

like image 33
Marty Avatar answered Oct 24 '22 11:10

Marty