I have a UITableView and I want to provide the functionality to user to delete the row when he slips or flicks his finger on the row. I know the editing style which provides a circular red button with -ve sign on it. But How to implement the flicking style. I saw many applications using it, so does apple provide any inbuilt delegate for it or we need to write our own controller for it.
In order to get the swipe affect you need to implement the table view delegate
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
method, this will provide access to the swipe interaction for deletion. I typically provide an edit interaction as well for tableviews where deletion is possible since the swipe interaction tends to be a little bit hidden from users.
As an example:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
[tableView beginUpdates];
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Do whatever data deletion you need to do...
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:UITableViewRowAnimationTop];
}
[tableView endUpdates];
}
Hope that helps.
I'm almost positive that there is a sample application that does this. A more concrete answer will come soon.
UPDATE: iPhoneCoreDataRecipes gives probably exactly what you're looking for.
On topic, here is one of the sweetest provided methods:
// If I want to delete the next 3 cells after the one you click
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
NSMutableArray* indexPaths = [NSMutableArray array];
for (int i = indexPath.row + 3; i < indexPath.row + 3; i++)
{
[indexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
}
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
[tableView deselectRowAtIndexPath:indexPath animated:YES];
}
I think that there is an easier way to accomplish what you want, but then again this is pretty easy. The only difficulty might be deleting yourself...just watch out for segfaults.
Related
In my application, I reload my TableView ([tablView reloadData];) after delete row from TableView then canEditRowAtIndexPath Method alway call for (pervious) total number Of Rows.
For Example:
If i have 5 Rows on my TableView, then i delete 1 row from tableView. After deleting, I reload my TableView ([tablView reloadData]) but canEditRowAtIndexPath Method calls 5 time instead of 4 times ??
So i always got Following Error:
Terminating app due to uncaught exception 'NSRangeException', reason: '* **-[__NSArrayM objectAtIndex:]: index 5 beyond bounds [0 .. 4]'
I also tried to reload table after some delay (using NSTimer) but it also not worked for me.
I put some code here:
I apply canEditRowAtIndexPath on specific row which #"status" isEqualToString:#"directory" such like,
- (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
{
NSLog(#"%d", self.listOfSounds.count);
// Return NO if you do not want the specified item to be editable.
if([[[self.listOfSounds objectAtIndex:indexPath.row] objectForKey:#"status"] isEqualToString:#"directory"])
return YES;
else
return NO;
}
Code of delete row:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[self.sql_ deleteSoundFileAudioTableWhereMainID:[[self.listOfSounds objectAtIndex:indexPath.row] objectForKey:#"main_id"]]; /// delete record from DB
[self.listOfSounds removeObjectAtIndex:indexPath.row]; /// delete record from Array
[self updateListofSoundsFile]; /// Custom method
}
}
- (BOOL)tableView:(UITableView *)tableView shouldIndentWhileEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
return NO; // i also tried to return YES;
}
Here updateListofSoundsFile is my custom method code is :
-(void)updateListofSoundsFile
{
if(self.listOfSounds.count > 0)
[self.listOfSounds removeAllObjects];
self.listOfSounds = [self.sql_ getAllDataFromAudioTable]; // get all record from DB
NSLog(#"%d",self.listOfSounds.count);
[self.tblView reloadData];
}
Please Give any suggestion, How can i solve this issue ?
Thanks :)
you need to remove raw from tableview also befor remove item from array and reload data using this line becouse remove item from array but not tableview.
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[self.sql_ deleteSoundFileAudioTableWhereMainID:[[self.listOfSounds objectAtIndex:indexPath.row] objectForKey:#"main_id"]]; /// delete record from DB
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[self.listOfSounds removeObjectAtIndex:indexPath.row]; /// delete record from Array
[self updateListofSoundsFile]; /// Custom method
}
}
I ran into this same problem. The issue was that I deleted the object of the cell but when I used the reloadData method of the tableView, my canEditRowAtIndexPath method was not being called resulting in being able to edit cells that I do not want edited. The true fix was not calling the deleteRowsAtIndexPaths method, but the [tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; method.
Basically here is what was happening:
When I called reloadData:
The raw data was not being removed from the tableView as Nitin said. Therefore this is not the solution.
When I called deleteRowsAtIndexPaths:
iOS detected a discrepancy between the underlying data and the number of cells (because I had already removed the underlying object). The result was a crash which is also not the solution (obviously).
Now for The Fix!
When I called reloadRowsAtIndexPaths:
This caused the tableView to simply reload that single cell AND it got rid of the raw data. This is the solution.
Rather than removing the dataSource object, then trying to remove the cell that is essentially backed by nothing at this point (which causes a crash), simply remove the dataSource object, then reload that indexPath of the tableView
Here is the general format of the method:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[myDataSourceArray removeObjectAtIndex:indexPath.row];
[tableView reloadRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
}
}
This is the first time I ran into the issue with the residual raw data that is not removed by simply calling the reloadData method. This is certainly the most elegant solution that I have seen thus far.
Happy Coding! I hope this helps.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[self.sql_ deleteSoundFileAudioTableWhereMainID:[[self.listOfSounds objectAtIndex:indexPath.row] objectForKey:#"main_id"]]; /// delete record from DB
[self.listOfSounds removeObjectAtIndex:indexPath.row]; /// delete record from Array
[self updateListofSoundsFile]; /// Custom method
}
[self.tblView reloadData];
}
When it comes to me, I analysed as below:
As commented by rmaddy on Nitin Gohel's answer:
You should always remove the data from the data source before updating the table.
I feels this is the ideal way.
If you are going to call reloadData, there is no reason at all to first call deleteRowsAtIndexPath.
This is also looks correct.
I analysed ambiguity, if I write reloadData its crashishing but once I write deleteRowsAtIndexPath it worked well.
Hope someone will emphasise on this issue for its causes etc.
the 'removeObjectAtIndex:indexPath' takes some time and I suspect your [self.tblView reloadData] is being called early. I tried a sample code and found success with [UiTableView beginUpdates] and [UiTableView endUpdates] you may also avoid crash if you put a little delay before the reloading or deleting rows haven't tried it though
[tableTable beginUpdates];
[tableArray removeObjectAtIndex:indexPath.row];
[tableTable deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationFade];
[tableTable endUpdates];
Can some one suggest what is the problem in my code.....
self.modleClass.foldersAray have one object and tableview have one section and one row
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[self.tableview setEditing:YES animated:YES];
[self.modleClass.foldersAray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YEs];
[tableView reloadData];
}
}
I am getting error as below.
*** 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).'
Do like this,
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
if (editingStyle == UITableViewCellEditingStyleDelete) {
[self.modleClass.foldersAray removeObjectAtIndex:indexPath.row];
[tableView reloadData];
}
}
it is more enough.........
If your tableView is based on the foldersAray, then removing an object from this array and reloading the tableview will result in the deletion of this cell.
You could remove the line:
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YEs];
This error means, that you must update you datasource (array with data, dictionary or whatever you use)
For example:
You have 5 cells before delete and 5 object in dataArray. Before call to deleteRowsAtIndexPaths: we must remove object from dataArray, that associated with this cell and only then call deleteRowsAtIndexPaths:. Also instead use [tableView reloadData], use
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:#[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
[tableView endUpdates];
P.S. I see, that you remove object from your datasource, so check it carefully, maybe you miss something
If you want animation try this, if you dont want animation use #Mountain Lion's answer.
-(void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete)
{
[self.modleClass.foldersAray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath]withRowAnimation:UITableViewRowAnimationFade];
}
}
And if you have edit button on navigation, you should call
[self.tableView setEditing:YES animated:YES];
inside of your method to start editting.
IF the memory it takes does not matter for you... then you can dramatically delete (hide) cell by setting height to 0.
-(CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
if (indexPath.row%2) {
return 0;
}
return 100;
}
P.S: above dummy code will dramatically delete alternate cells.
I want to remove a specific cell from a UITableView
I found deleteRowsAtIndexPaths:withRowAnimation:
I tried to change the cell.frame to 0,0,0,0
I tried to .hidden = YES it
nothing works for me.
Any ideas?
Thank you!
Try adding the begin and end calls for editing:
[tableView beginUpdates];
[tableView deleteRowsAtIndexPaths:deletePaths withRowAnimation:UITableViewRowAnimationFade];
[tableView endUpdates];
Alternatively, if you are working with an underlying data source, remove the object from the datasource and call [tableView reloadData];
Use this method to remove the cell from your table view and hand to hand remove the same objects from your array by which you are showing data on tableview.
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if (editingStyle == UITableViewCellEditingStyleDelete) {
// Delete the row from the data source
[yourArray removeObjectAtIndex:indexPath.row];
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:YES];
}
[tableView reloadData];
}
Hope this will help you..
I can't figure out why my tableView isn't updating after I tap the delete button.
Once I click it, the table view "freezes". If I click another row, so that the tableview goes to another level of the hierarchy and click back, I can see that the item has been deleted and everything works fine.
Here is my code:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
{
//[tableView beginUpdates];
if (editingStyle == UITableViewCellEditingStyleDelete)
{
// Do whatever data deletion you need to do...
[tableView beginUpdates];
NSManagedObject *obj = (NSManagedObject *)[entityArray objectAtIndex:indexPath.row];
[managedObjectContext deleteObject:obj];
NSError *error;
[managedObjectContext save:&error];
// Delete the row from the data source
[tableView deleteRowsAtIndexPaths:[NSArray arrayWithObjects:indexPath, nil] withRowAnimation:YES];
[self viewWillAppear:YES];
}
//[tableView endUpdates];
}
Any input on this problem would be appreciated.
Thanks.
Found same issue and stumbled upon this thread. But the reason for the tableview freeze issue was different in our case.
For the sake of posterity:
The UITableViewCell which goes into Edit mode to display the "insert" or "delete" buttons should never have its userInteractionEnabled property set to "NO".
By correcting this, the same tableview freezing issue was fixed for us.
I can't see a call to [tableView endUpdates] matching the [tableView beginUpdates] that is at start of the if.
Could it be for this reason that your table freezes?
I'm having a UITableView with alternating colored UITableViewCells. And the table can be edited: rows can be reordered and deleted. How do I update the cells alternating background color, when the rows get reordered or deleted?
I'm using this to draw the alternating colored cells:
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
if ([indexPath row] % 2) {
// even row
cell.backgroundColor = evenColor;
} else {
// odd row
cell.backgroundColor = oddColor;
}
}
But this method is not being called when a row gets reordered or deleted. And I can't call [tableView reloadData] from the following method, because it crashes the app in an infinite loop:
- (void)tableView:(UITableView *)tableView moveRowAtIndexPath:(NSIndexPath *)fromIndexPath toIndexPath:(NSIndexPath *)toIndexPath {
// Move the object in the array
id object = [[self.list objectAtIndex:[fromIndexPath row]] retain];
[self.list removeObjectAtIndex:[fromIndexPath row]];
[self.list insertObject:object atIndex:[toIndexPath row]];
[object release];
// Update the table ???
[tableView reloadData]; // Crashes the app in an infinite loop!!
}
Does anybody have a pointer or a best practices solution to deal with the issue of reordering alternating colored cells?
Thanks
Used a delayed call to perform the reload if you can't call from that method:
[tableView performSelector:#selector(reloadData) withObject:nil afterDelay:0.0f];
It waits until after your current method is finished before it calls reload.
No needs to use third-party objects or reload/refresh the whole dataSource. Just use the right tools in your swiss knife:
- (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
if(editingStyle == UITableViewCellEditingStyleDelete) {
//1. remove your object
[dataSource removeObjectAtIndex:indexPath.row];
//2. update your UI accordingly
[self.myTableView beginUpdates];
[self.myTableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:indexPath] withRowAnimation:UITableViewRowAnimationRight];
[self.myTableView endUpdates];
//3. obtain the whole cells (ie. the visible ones) influenced by changes
NSArray *cellsNeedsUpdate = [myTableView visibleCells];
NSMutableArray *indexPaths = [[NSMutableArray alloc] init];
for(UITableViewCell *aCell in cellsNeedsUpdate) {
[indexPaths addObject:[myTableView indexPathForCell:aCell]];
}
//4. ask your tableview to reload them (and only them)
[self.myTableView reloadRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
}
}
Reload is too heavyweight; I've written AltTableViewController that just changes background color of cells and it should work faster.
I took all the UITableViewCell subviews from the tableview and sorted that array based on cells frame.origin.y so they were back in the proper order. Then I looped through changing the background color based on the index == 0 || index % 2 == 0 re-coloring them. Seems to work better than reloading the tableView as that was causing the animation to jerk. Worked for me at 1:25 AM
[tableView reloadData] will get your table backgrounds back in the swing of things. Your other option is to swap the background colors of the all visible cells from the indexPath of the lower index in the move on up to the highest in visibleCells.
This works nice. Start the pattern at the last index rather than the first. That way each cell always retains it's background:
if (dataSource.count - indexPath.row) % 2 == 0 {
cell.backgroundColor = UIColor.grayColor()
} else {
cell.backgroundColor = UIColor.whiteColor()
}
I tried the other solutions, but wasn't completely satisfied. This solution is not a hack and doesn't even add another line of code.
If you're using swift and NSFetchedResultsController:
func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
tableView.endUpdates()
//for the alternate colours
self.tableView.reloadRows(at: tableView.indexPathsForVisibleRows!, with: .fade)
}