I\'ve got some rather complicated rules for moving rows around in a UITableView
. There are an undefined number of sections and rows per section, and based on v
I just hit what I believe is the same problem in my app.
The situation is that I have two table sections. Items can be dragged within and between sections. Users can drag cells to any row in the first section, but in the second section the items are sorted, so for any given cell, there's only one valid row.
If I scroll the view so that the bottom of section 1 and the top of section 2 are visible, grab an item in section 1 that sorts to the bottom of section 2, and drag it into the the top of section 2, my tableView:targetIndexPathForMoveFromRowAtIndexPath:toProposedIndexPath:
method gets called and I return the correct destination position, which is several rows below the bottom of the screen. In the UI, you can see an empty cell gets created at the bottom of the screen, which is not the correct destination row.
When you let go of the cell, that bogus cell that was created at the bottom of the screen (in the middle of section 2) stays there! tableView:cellForRowAtIndexPath:
never even gets called for it. As soon as you try to do anything with that cell, you crash.
My first solution was to just call [tableView reloadData] at the end of tableView:moveRowAtIndexPath:toIndexPath:
. But that causes a crash, so instead I call it indirectly after a delay. But then there's another bug: after the delayed reloadData call, tableView:moveRowAtIndexPath:toIndexPath:
gets called again with a bogus request to move an item one past the end of the first section to that same position. So, I had to add code to ignore bogus no-op requests.
So, here's the code:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)pathSrc toIndexPath:(NSIndexPath *)pathDst
{
// APPLE_BUG: after doing the delayed table reload (see below), we get a bogus
// request to move a nonexistant cell to its current location
if (pathSrc.row == pathDst.row && pathSrc.section == pathDst.section)
return;
// update your data model to reflect the move...
// APPLE_BUG: if you move a cell to a row that's off-screen (because the destination
// has been modified), the bogus cell gets created and eventually will cause a crash
[self performSelector:@selector(delayedReloadData:) withObject:tableView afterDelay:0];
}
- (void)delayedReloadData:(UITableView *)tableView
{
Assert(tableView == self.tableView);
[tableView reloadData];
}
Note that there's still a UI bug. On the screen, the dragged cell gets animated into the bogus empty cell. At the end of the animation, the empty cell gets redrawn with the correct data for that row, but the observant user will notice the dragged cell getting animated to the wrong spot then instantly morphed to a different cell.
This is definitely a goofy UI. I considered scrolling the proper destination row onto the screen, but if I were to do that it would fill the screen with section two and then any attempt to drag back to section one would be continually thwarted by my (now annoying) autoscrolling. I may have to change the UI, but that would require some complex and bothersome changes to my data model.