Handling "swipe to delete" mode


#1

UITableView behaves differently when “swipe to delete” is invoked. The tableview does not invoke the delegate method tableView:editingStyleForRowAtIndexPath.
The sample project presents the “Add New Item…” cell that cannot be used to add a new row as touch events received anywhere outside of the “Delete” button in the “swiped” cell result in the tableView resetting the editing property to NO.

No sense presenting something that cannot be used, so I made some modifications to prevent this from occurring.

  1. Add an ivar to ItemsViewController to keep track of when the table view is in “swipe to delete” mode.
  2. Modify init to set “swipe to delete” mode as NO. (not shown below)
  3. Implement UITableViewDelegate methods tableView:willBeginEditingRowAtIndexPath: and tableView:didEndEditingRowAtIndexPath:
  4. Modify tableView:numberOfRowsInSection to increment numberOfRows if the tableView is in edit mode and not in swipe to delete mode.
  5. Modify setEditing:animated: so that it won’t attempt to add or remove a row from the tableView if it is in “swipe to delete” mode.
// ItemsViewController.h

@interface ItemsViewController : UITableViewController
{
    UIView *headerView;
    NSMutableArray *possessions;
    BOOL isInSwipeDeleteMode; // use to track the entry and exit from swipe delete mode. No public property available
}
@property (assign) BOOL isInSwipeDeleteMode;
- (UIView *)headerView;
@end

// ItemsViewController.m

- (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self setIsInSwipeDeleteMode:YES];
    [self setEditing:YES animated:YES];
}

- (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath
{
    [self setEditing:NO animated:YES];
    [self setIsInSwipeDeleteMode:NO];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    int numberOfRows = [possessions count];
    // If we are editing but not in swipe to delete mode, we will have one more row than possessions
    if ([self isEditing] && ![self isInSwipeToDeleteMode])
        numberOfRows++;
    return numberOfRows;
}

- (void)setEditing:(BOOL)editing animated:(BOOL)animated
{
    // Always call super's implementation...
    [super setEditing:editing animated];

    // If we are in "swipe to delete" mode, nothing left to do
    if ([self isInSwipeToDeleteMode])
        return; // added to sample code

    // You need to insert/remove an new row
    if (editing) {
        // If entering edit mode (but not from a swipe) add another row.
        // The rest of the sample code is the same
}

Now the tableView will only show the “Add New Item…” row in response to editButtonPressed:
This will also work now when we use the navigationItem edit/done button.


#2

Hi,

If you look over here - Entering Editing Mode via swipe, you’ll se that we discussed the same problem just a few days ago. We kinda solved it, but now I think your solution is much better because it doesn’t involve changing an action of a stock object and is actually documented by Apple. In header file UITableView.h there are these lines:

[code]// The willBegin/didEnd methods are called whenever the ‘editing’ property is automatically changed by the table (allowing insert/delete/move). This is done by a swipe activating a single row

  • (void)tableView:(UITableView*)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath;
  • (void)tableView:(UITableView*)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath;
    [/code]

Thanks for sharing.

And since you haven’t posted changes to -setEditing:animated: I suppose you have something like this:

[code]- (void)setEditing:(BOOL)flag animated:(BOOL)animated
{
[super setEditing:flag animated];

NSIndexPath *idxPath = [NSIndexPath indexPathForRow:[self.possessions count] inSection:0];
NSArray *paths = [NSArray arrayWithObject:idxPath];

if (flag) {
if (!isInSwipeDeleteMode)
[[self tableView] insertRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationLeft];
}
else {
if (!isInSwipeDeleteMode)
[[self tableView] deleteRowsAtIndexPaths:paths withRowAnimation:UITableViewRowAnimationFade];
}
}
[/code]


#3

EDIT:Turns out that we need to modify setEditing:animated: when we use a navigationItem

Good catch, changes to the setEditing method of ItemsViewController are needed when using navigationItem. Set a breakpoint on the method. You will see that the view controller’s implementation of the method is not called on a “swipe to delete” however touching the Edit button in the navigation bar will call setEditing:animated. My first attempt caused an exception because the code was attempting to add/remove the “Add Item…” row

I noticed in the Mail app that the Edit button toggles to Done after a swipe. I updated the delegate methods to call setEditing:animated: to get the same behaviour.

I have updated the original post so that the views are updated properly.

Apple’s TableView Programming guide has a paragraph that talks about how the editing property of the UITableViewController is obtained by asking it’s tableView. That is why the sample calls super’s implementation of setEditing:animated:
If it didn’t, the tableView’s editing property would not update and trigger the changes to the view.


#4

Hi,

That’s where I got to in my second version but couldn’t find a way of changing the Header button to Done after the swipe.

I did have to amend setEditing as well though as pressing Edit after the swipe caused all sorts of merry hell !

It works ok but pressing Edit at that point takes it out of Edit mode and so it isn’t an intuitive title.

Anyone know how to do this?

Gareth


#5

Thanks for pointing out that I missed the required update to setEditing:animated.


#6

Hi,

I’m still not there with this one yet.

I find that didEndEditingRowAtIndexPath is not getting triggered when you press the Edit / Done button in the header.

Consequently the swiped boolean doesn’t get reset and you’re then out of sync.

Launch Application
Press Edit
Add New Row appears. Header button set to Done =---- Good so far.
Press Done - Exits editing mode----Still good.
Swipe a row - willBeginEditingRowAtIndexPath is triggered so swiped boolean is set to true and Delete button appears on the right — Still good.
Press Edit / Done in header - didEndEditingRowAtIndexPath is not triggered. swiped boolean still true. Exits editing mode.—Looks good but isn’t !
Press Edit in header. Swiped boolean is still true so Add New Row doesn’t appear. ---- Not Good.

Are you getting different behaviour to this?

Gareth


#7

Have you seen my post in the other thread about this problem? Does that solve the problems?


#8

Hi,

Yes - I can see why it should work if my didEndEditingRowAtIndexPath was being triggered.

I also took the code from the download area and applied all the steps again just in case I’d done something to my own code.

Same problem.

I can get didEndEditingRowAtIndexPath to trigger if I click the row or the background but not when I click the Edit button.

Gareth


#9

But it is documented that bar button will never call -tableView:didEndEditingRowAtIndexPath:.

You should set that instance flag in -tableView:willBeginEditingRowAtIndexPath: and reset it back in -setEditing:animated: when that editing parameter is NO. This assumes you are calling that method from -tableView:didEndEditingRowAtIndexPath:.

I think that should solve all of the problems in that code. I can not post my code because I am in the office now, and I have the project at home, but I could post all of it later in the evening if you wish.


#10

Hi,

I’m in the office too but from memory it’s probably the reset in setEditing that I’m missing.

I’ll give it a go tonight and let you know.

Much appreciated.

Gareth


#11

Curiously enough, there is this iPhone Programming book with Whereami project mentioned on page 87 and that book has UITableView example with very similar swiping problem. Author of that book, Alasdair Allan is not subclassing UITableView in his root view controller, but just basic UIViewController. I have noticed this because in his example he calls -setEditing:animated: method on both the superclass (which is UIViewController) and then in the next line on the tableView.

Well, I won’t put a link to another book on this forum, I suppose it wouldn’t be nice to our hosts, but googling around and downloading the code for the 5th chapter might turn useful for the more curious.


#12

Hi,

Success !!!

Idelovski - the answer for me was in “reset it back in -setEditing:animated: when that editing parameter is NO.”

Thanks for persevering - I seemed to have a mental block with this one.

I’ll go and check out that other book too.

Gareth


#13

Well, thank you.

It took all three of us to solve this thing. As I was reading that chapter in the book it all looked way too complicated for something so basic as adding an item. I remember thinking that I’ll probably never have it like this. Putting an “Add” button in the navigation bar seemed so much simpler. But, the problem is there isn’t much space in that bar. In a real application, left side would be taken by the back button and on the right there is a place for only a single item.

In the end, I am glad we solved this properly.


#14

Yes, it would be much simpler to put the Add button in the navigation bar, in fact, earlier edits of the book did exactly that. However, when teaching our iPhone Bootcamp, a lot of the students were interested in how to conditionally add a row to a table view, similar to the way the Address Book does. So, I went to solve the problem for them and walk them through it in class, but it turned out to be a much bigger undertaking than a simple “Here, do this and this and then you’re done.” When I googled for resources on the technique, I found the ratio of “How do I do this?” to “Here is how you do it” to be pretty much infinite, so I decided it would be a good idea to use this more difficult technique. Unfortunately, I didn’t realize the swipe-to-delete “mode” actually toggled editing mode until way too late in the game. But glad you guys got a solution. :slight_smile:


#15

For me, it complained that isInSwipeToDeleteMode and setIsInSwipeToDeleteMode were missing, so I had to add

to ItemsViewController.m


#16

I had this post initially in another section of the forum, but I then found this thread inside this section, so put it up in here. I’ve left it as it was written in the other section, though after reading through this thread, some insight was provided.

What I discovered is swipe to delete is active by default, and in my implementation it would, like others pointed out, bring up the “add new item…” row. I stumbled upon what you see below as my solution, and it kinda floored me…

8<---------

I decided to fully implement swipe-to-delete after stumbling upon it being active by default in my application, but not working properly.

So, when I initially began the implementation, swiping across the screen would cause the “add new…” row to appear in my table. I thought that was a little odd from the user’s point of view, so did some research, and discovered the tableView:willBeginEditingRowAtIndexPath:indexPath and tableView:didEndEditingRowAtIndexPath:indexPath methods.

So, after researching and tracing through the program, step by step, I found that this, below, is the implementation that makes it work like I want it to work:

[code]-(void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath {

}

-(void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
}
[/code]

Yes, folks, that’s right, two methods that do absolutely nothing!

Wait a minute…

nothing???

Huh?

Can someone please shed some light on why I would need to implement two completely (in this particular implementation), to make my program properly implement swipe-to-delete, I have to create two do-nothing method?


#17

I actually don’t know in this specific case, but sometimes (and I would bet this is one of those times), an object has some default behavior. If its delegate implements a particular method, it substitutes in the delegates implementation for its own default behavior.

So, it may go something like:


- (void)editRowAtIndexPath:(NSIndexPath *)ip
{
    if([[self delegate] respondsToSelector:@selector(tableView:willBeginEditingRowAtIndexPath:)])
    {
         [[self delegate] tableView:self willBeginEditingRowAtIndexPath:ip];
    }
    else 
    {
        [self showDeleteControlForRowAtIndexPath:ip];
    }
}