I'm trying to implement a custom deletion process for my app, as my costumer doesn'y want the red circle provided by table view edition mode. I have added a deletion button for every row, with row number in its tag property. Once user clicks deletion button, it fires following method:
-(IBAction)deleteRow:(id)sender{
UIButton *tempButton = (UIButton*)sender;
[self updateTotals];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:tempButton.tag inSection:0];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView reloadData];
[sharedCompra removeItem:tempButton.tag];
tempButton=nil;
}
And I allways get this error:
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 (3), 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).'
So I don't know if I'm missing something in this piece of code.
Many thanks.
You're trying to delete a row whereas your data source still reflects the original state (i. e. that before the deletion). You have to update the data source first, it's only then that you can issue a deletion from the table viev. You also don't need to set the button to nil, nor do you need to call - reloadData on the table view:
- (void)deleteRow:(id)sender
{
UIButton *tempButton = sender;
[self updateTotals];
NSIndexPath *indexPath = [NSIndexPath indexPathForRow:tempButton.tag inSection:0];
[sharedCompra removeItem:tempButton.tag];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
}
(One thing you should do, however, is paying attention to how you format your code.)
Call [sharedCompra removeItem:tempButton.tag]; before [tableView deleteRowsAtIndexPaths... and [tableView reloadData]
The problem is that when you call deleteRowsAtIndexPath: it is calling numberOfRowsInSection which returns the same count from your model.
Also reloadData call is not needed here.
When deleting and adding rows, you need to call beginUpdates, here how it should look.
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
and you don't need to call reloadData, or it will cancel the animation of the deleteRows, and insertRows methods. But you do need to reset the data, so if you're using an NSMutableArray, you need to remove the object first, so item at index 0, then you can delete row 0 in table. the number of rows needs to match the number of objects in that array when it ends deleting or it will also crash.
Related
I'm losing my mind here. Relevant code:
-(NSInteger)numberOfSectionsInTableView:(UITableView *)tableView{
return [[self.currentWorkout movements] count];
}
And...
- (void)addMovement:(Movement *)movement{
[self.currentWorkout.movements insertObject:movement atIndex:0];
[self.table beginUpdates];
NSIndexPath *path = [NSIndexPath indexPathForRow:0 inSection:0];
[self.table insertRowsAtIndexPaths:#[path] withRowAnimation:UITableViewRowAnimationTop];
[self.table endUpdates];
}
I have confirmed that insertObject:atIndex: is adding the object; the count of currentWorkout increases by one.
Error: 'NSInternalInconsistencyException', reason: 'Invalid update: invalid number of sections. The number of sections contained in the table view after the update (8) must be equal to the number of sections contained in the table view before the update (7), plus or minus the number of sections inserted or deleted (0 inserted, 0 deleted).'
Solved: using wrong method to insert sections.
- (void)addMovement:(Movement *)movement{
[self.currentWorkout.movements insertObject:movement atIndex:0];
NSLog(#"%d",(int)[self numberOfSectionsInTableView:self.table]);
[self.table beginUpdates];
[self.table insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationTop];
[self.table endUpdates];
}
Removing the [self.table beginUpdates]; method call is important because otherwise the table will think it has the number of rows it would get after adding the object before that object is actually added.
In my app i am using two table one for filter the data and second for results data. I am using the following line to scroll the tableview at selected index position,
self.indexPaths = [NSIndexPath indexPathForRow:0 inSection:indexPath.row];
[tableview scrollToRowAtIndexPath:self.indexPaths atScrollPosition:UITableViewScrollPositionTop animated:YES];
[tableview reloadData];
but my app is getting crash in only in iPhone5(iOS7) and working fine in all devices even iPhone5(iOS6).
Why does this crash only on iPhone5(iOS7)?
Crashing log is: Terminating app due to uncaught exception 'NSInvalidArgumentException'
You pass integer (0) for index path. You should pass NSIndexPath instead, try:
NSIndexPath *ip = [NSIndexPath indexPathForRow:0 inSection:0];
[tableview scrollToRowAtIndexPath:ip atScrollPosition:UITableViewScrollPositionTop animated:NO];
NSIndexPath is an object so you can't pass 0.
Try [NSIndexPath indexPathWithIndex:0]
self.indexPaths = [NSIndexPath indexPathForRow:0 inSection:indexPath.row];
the RHS returns the indexpath corresponding to a row and section.
in the above code,
you are passing indexpath.row to inSection parameter. This may be wrong.Assume, you have 1 section and 5 rows. when indexpath.row = 4(for eg), there wont be a section corresponding to 4 and hence self.indexPaths becomes nil since the system would try to look for row 4 in section 4, but section 4 doesnt exist!!
Consequently, when you pass nil to below line, it says nsinvalidargument exception.
tableview scrollToRowAtIndexPath:self.indexPaths atScrollPosition:UITableViewScrollPositionTop animated:YES];
I have a table that when you select an item, it changes some stuff about the object in that cell, and then moves the cell to the bottom, and then reloads the table data.
//update model
...
//move cell to bottom
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationBottom];
[tableView insertRowsAtIndexPaths:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:lastIndex inSection:0]] withRowAnimation:UITableViewRowAnimationTop];
[tableView endUpdates];
//reload cells
[tableView reloadData];
currently, it works, but doesn't wait for the animations to finish. How can I wait for the insert and delete animations to finish before reloading the data? or if there is a better way to do it, that would be nice to know as well.
Simple, instead of removing a cell and then immediately adding a new one, you could use UITableView's moveRowAtIndexPath which will animate the cell from its starting point to its ending point.
[tableView moveRowAtIndexPath:[NSArray arrayWithObject:indexPath] toIndexPath:[NSArray arrayWithObject:[NSIndexPath indexPathForRow:lastIndex inSection:0]]];
I'm working on a Core Data driven iPad app with a split view controller. Just imagine the iPad Mail app and you'll be on the right track. When I select a record in the Root View Controller, the details display in the DetailViewController.
On the Detail View, I have a delete button. When clicked, it tells its Core Data context to delete the current object. It performs the delete correctly and the row disappears from the RootViewController, as it should.
How can I get the RootViewController to automatically select the row after the row that was deleted so it subsequently displays the details in the detail view? (Or automatically select the previous row if the deleted row was the last row?)
If you use an NSFetchedResultsController to manage the table then you can use it's delegate methods to respond to changes.
Use the controller:didChangeObject:atIndexPath:forChangeType:newIndexPath: delegate method and check for the NSFetchedResultsChangeDelete change type.
You can see if the indexPath matches the currently selected rows in your table and then act upon that.
OK. I think I figured it out. Here's what I did. (Please chime in if you see a better way.)
First, I defined a new NSInteger ivar, lastSelectedRow.
Next, I changed
case NSFetchedResultsChangeDelete: {
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
break;
to
case NSFetchedResultsChangeDelete: {
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
lastSelectedRow = indexPath.row;
id <NSFetchedResultsSectionInfo> sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:0];
if (lastSelectedRow == [sectionInfo numberOfObjects]) {
--lastSelectedRow;
}
break;
}
Then I changed
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
}
to
- (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
{
[self.tableView endUpdates];
if (lastSelectedRow > -1) {
NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:lastSelectedRow inSection:0];
[self.tableView selectRowAtIndexPath:newIndexPath animated:YES scrollPosition:UITableViewScrollPositionNone];
[self tableView:self.tableView didSelectRowAtIndexPath:newIndexPath];
lastSelectedRow = -1;
}
}
Und voila!!
As an added bonus I can now store the last selected row when my app is terminated, and I can go right back to it next time it launches.
For my iPad project I need to be able to reload table rows.
This is how it should works, depend on some "id" selected from a popupview, i need to be able to reload table rows, table rows are dynamics and has a UITextField in it. I tried to use [self.tableView reload] but for some reason it does not update the rows correctly. Something like UITextField placeholder owned by previous "id" does not change. What I have in mind is to remove all cells in that specific section and reload the table with the new "id". When i do this i got this exception:
* 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 (3), plus or minus the number of rows inserted or deleted from that section (0 inserted, 3 deleted).'
The project code:
- (void)selectedArchiefID:(NSString *) value {
[self.popOverController dismissPopoverAnimated:YES];
// collects all available document indexfields
int rowCount = [indexDefinities count];
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
for (int curIndex=0; curIndex < rowCount; curIndex++) {
[indexPaths addObject:[NSIndexPath indexPathForRow:curIndex inSection:0]];
}
// deletes rows
if ([indexPaths count] > 0) {
[self.tableView beginUpdates];
[self.tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
//[self.tableView deleteSections:0 withRowAnimation:UITableViewRowAnimationNone];
[self.tableView endUpdates]; //crashes here
}
[self.tableView reloadData];
}
Any idea what i did wrong?
Looks like you forgot to change the data of the datasource of the tableview.
You can't just change the tableview layout.
If you want to remove a section, you have to remove this section from the datasource first.
You could do this the easy way, by removing the section in a NSMutableArray (which is your datasource).
Or you could write a big if then else monster that figures out how much sections (and rows) to return in numberOfSectionsInTableView: and tableView:numberOfRowsInSection:.
Doesn't matter how you do it, but you have to remove the data before you update the tableview.
One more thing, having both [self.tableView beginUpdates]; /*...*/ [self.tableView endUpdates]; and [self.tableView reloadData]; in one method is usually useless. THe latter would kill the animation effect you get from the first one.