views:

696

answers:

2

I have a table that displays data from a single array and organizes the items into separate sections based on a set of filters. Each filter has a corresponding section. My UI allows the user to tap a check box embedded in each table cell. This check box sets a "checked" flag which affects which section the item is filtered to. Here's an example:

  • Section 1
    • Item A
    • Item B
  • Section 2
  • Section "Done"

Tapping the check box next to Item A should cause the table to rearrange to look like this:

  • Section 1
    • Item B
  • Section 2
  • Section "Done"
    • Item A

Below is my code for responding to the check box tap. It figures out which row the check box belongs to and then attempts to create an animation block that deletes the row from its old section and adds a row to the end of the "Done" section. Please keep in mind that the array doesn't actually change -- the way the data is filtered into the sections changes based on the value of the items' checked properties.

- (IBAction)checkBoxTapped:(id)sender
{
 // Get the table cell
 CheckBoxControl* checkBox = (CheckBoxControl*)sender;

 UITableViewCell* cell = (UITableViewCell*)[[sender superview] superview];
 UITableView* table = (UITableView*)[cell superview];
 NSIndexPath* indexPath = [table indexPathForCell:cell];

 Item* item = [self itemAtRow:[indexPath row] inSection:[indexPath section]];
 item.checked = checkBox.checked;

 Filter* filter = [filters objectAtIndex:DONE_INDEX];
 // We want the new row at the bottom of the "Done" section.
 NSIndexPath* newIndexPath = [NSIndexPath indexPathForRow:[self countItemsWithFilter:filter] inSection:DONE_INDEX];

 [table beginUpdates];
 [table insertRowsAtIndexPaths:[NSArray arrayWithObject:newIndexPath] withRowAnimation:UITableViewRowAnimationFade];
 [table deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
 [table endUpdates];
}

This code always throws an exception:

2010-01-13 00:19:44.802 MyApp[19923:207] *** Terminating app due to uncaught
exception 'NSRangeException', reason:
'*** -[NSMutableIndexSet addIndexesInRange:]:Range {2147483647, 1}
exceeds maximum index value of NSNotFound - 1'

Can anyone help me figure out what is causing the NSRangeException? Here's my stack:

___TERMINATING_DUE_TO_UNCAUGHT_EXCEPTION___
obj_exception_throw
+[NSException raise:format:arguments:]
+[NSException raise:format:]
-[NSMutableIndexSet addIndexesInRange:]
-[NSMutableIndexSet addIndex:]
-[_UITableViewUpdateSupport(Private) _computeRowUpdates]
-[_UITableViewUpdateSupport initWithTableView:updateItems:oldRowData:oldRowRange:newRowRange:context:]
-[UITableView(_UITableViewPrivate) _updateWithItems:withOldRowData:oldRowRange:newRowRange:context:]
-[UITableView(_UITableViewPrivate) _endCellAnimationsWithContext:]
-[UITableView endUpdates]
-[RootViewController checkBoxTapped:]

If this code is just plain wrong then how should I animate removing a row from one section and adding it to another?

Thanks in advance for any help you can give me.

-Mike-

A: 

Calling -insertRowsAtIndexPaths and -deleteRowsAtIndexPaths you only tell UITableView to update corresponding rows with animation. So the possible problem is that you should also update your data source accordingly to your changes (e.g. -numberOfRowsInSection must return the actual values etc.)

Vladimir
I wrote -numberOfRowsInSection to ask the section's filter how many items match. The item's "checked" property is changed before calling -insertRowsAtIndexPaths and -deleteRowsAtIndexPaths so the filters will report the correct number of items for each section. I put a break point in -numberOfRowsInSection and verified this.I designed the data source so the array is not modified. Every section shows items from the same copy of the array and the filters determine which items each section shows based on the values of the items. Does that make sense?
Mike H.
Yes this way looks ok, does it work? :)
Vladimir
No. When I try I get the exception every time. I'm trying some other approaches in a test environment to see if I can narrow it down.
Mike H.
+1  A: 

Ok, I think I figured out a better approach. Instead of trying to populate the sections using a filtering function I decided to create an array for each section. Each section's array contains indexes into the data source array. It looks something like this:

NSArray* data = [[NSArray alloc] initWithObjects:@"Apple", @"Banana", @"Cat", @"Dog", nil]];
NSMutableArray* doneFilter = [[NSMutableArray alloc] init];
NSMutableArray* othersFilter = [[NSMutableArray alloc] initWithObjects:[NSNumber numberWithInt:0],
                                [NSNumber numberWithInt:1], [NSNumber numberWithInt:2],
                                [NSNumber numberWithInt:3], nil];

Now, when the user taps the checkbox I remove the item's index from the othersFilter and add it to the bottom of the doneFilter. I then remove and add the corresponding rows in the table view. This seems much more stable than what I had before (no exceptions thrown!!) and has the added benefit of allowing the filters to have ordering of their own.

Mike H.