Adam Alexander
Adam Alexander

Reputation: 15180

Animating custom-drawn UITableViewCell when entering edit mode

Background

First of all, much gratitude to atebits for their very informative blog post Fast Scrolling in Tweetie with UITableView. The post explains in detail how the developers were able to squeeze as much scrolling performance as possible out of the UITableViews in Tweetie.

Goals

Beginning with the source code linked from the blog post (original) (my github repo):

  1. Allow a UITableView using these custom cells to switch to edit mode, exposing the UI for deleting an item from the table. (github commit)

  2. Move the cell's text aside as the deletion control slides in from the left. This is complete, although the text jumps back and forth without animation. (github commit)

  3. Apply animation to the text movement in goal 2 above for a smooth user experience. This is the step where I became stuck.

The Question

What is the best way to introduce this animation to complete goal 3? It would be nice if this could be done in a manner that keeps the logic from my last commit because I would love the option to move the conflicting part of the view only, while any non-conflicting portions (such as right-justified text) stay in the same place or move a different number of pixels. If the above is not possible then undoing my last commit and replacing it with an option that slides the entire view to the right would be a workable solution also.

I appreciate any help anyone can provide, from quick pointers and ideas all the way to code snippets or github commits. Of course you are welcome to fork my repo if you would like. I will be staying involved with this question to make sure any successful resolution is committed to github and fully documented here. Thanks very much for your time!

Updated thoughts

I have been thinking about this a lot since my first post and realized that moving some text items relative to others in the view could undo some of the original performance goals solved in the original blog post. So at this point I am thinking a solution where the entire single subview is animated to its new postion may be the best one.

Second, if it is done in this way there may be an instance where the subview has a custom color or gradient background. Hopefully this can be done in a way that in its normal position the background extends unseen off to the left enough so that when the view is slid to the right the custom background is still visible across the entire cell.

Upvotes: 49

Views: 27022

Answers (6)

Henry
Henry

Reputation: 479

There are actually four edite states.

UITableViewCellStateDefaultMask = 0

UITableViewCellStateShowingEditControlMask = 1<<0 = 0b01 = 1 by setting the tableView.edit = YES

UITableViewCellStateShowingDeleteConfirmationMask = 1<<1 = 0b10 = 2 by swiping to delete

UITableViewCellStateShowingEditControlMask and UITableViewCellStateShowingDeleteConfirmationMask = (1<<1)&(1<<0) = 0b11 = 3 by setting the tableView.edit = YES and press the "stop" button

What confused me is that when state 3 transfer to state 1, no selector would get called which make no use of self.showingDeleteConfirmation to change layout in iOS7.Therefore, in order to make clear between state 2 and state 3, I added a instance variable to implement it. It works great for me.

-(void)willTransitionToState:(UITableViewCellStateMask)state{
    [super willTransitionToState:state];

    //Custom delete button from
    //http://stackoverflow.com/questions/19159476/uitableviewcelldeleteconfirmationcontrol-issue
    if((state & UITableViewCellStateShowingDeleteConfirmationMask) == UITableViewCellStateShowingDeleteConfirmationMask){
        [self recurseAndReplaceSubViewIfDeleteConfirmationControl:self.subviews];
        [self performSelector:@selector(recurseAndReplaceSubViewIfDeleteConfirmationControl:) withObject:self.subviews afterDelay:0];
    }

    _swipeDelete = NO;

    if(state==2){
        //Swipe to delete confirm
        _swipeDelete = YES;
    }

    if(state==3){
        //Edit state to delete confirm
    }
}

- (void)layoutSubviews{

    [super layoutSubviews];

    self.contentView.frame = CGRectMake(10, 5.0, 300, self.frame.size.height - 10);

    if(self.editing){
        self.contentView.frame = CGRectMake(40, 5.0, 270, self.frame.size.height - 10);
    }

    if(_swipeDelete){
        self.contentView.frame = CGRectMake(10, 5.0, 300, self.frame.size.height - 10);
    }
}

Upvotes: 0

UIBuilder
UIBuilder

Reputation: 1333

For fully control on editing in your custom cell, you should override willTransitionToState method in your UITableViewCell subclass and check state mask

- (void)willTransitionToState:(UITableViewCellStateMask)state
{
    NSString *logStr = @"Invoked";
    if ((state & UITableViewCellStateShowingEditControlMask)
        != 0) {
        // you need to move the controls in left
        logStr = [NSString stringWithFormat:@"%@
                  %@",logStr,@"UITableViewCellStateShowingEditControlMask"];
    }
    if ((state & UITableViewCellStateShowingDeleteConfirmationMask)
        != 0) {
        // you need to hide the controls for the delete button
        logStr = [NSString stringWithFormat:@"%@
                  %@",logStr,@"UITableViewCellStateShowingDeleteConfirmationMask"];
    }
    NSLog(@"%@",logStr);
    [super willTransitionToState:state];
}

also you can override layoutSubviews

- (void)layoutSubviews {
    // default place for label
    CGRect alarmTimeRect = CGRectMake(37, 7, 75, 30);
    if (self.editing && !self.showingDeleteConfirmation) {
        // move rect in left
        alarmTimeRect = CGRectMake(77, 7, 75, 30);
    }
    [alarmTimeLabel setFrame:alarmTimeRect];
    [super layoutSubviews];
}

Upvotes: 9

Sauleil
Sauleil

Reputation: 2603

I just had this question too: how to animate a custom editing mode? I didn't really like the solution here, so I decided to think a little bit and found a different solution. I don't know if it's better, but I prefer that one. So I decided to share it here:

In the custom cell (inherit from UITableViewCell), just overload the setEditing:

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

    if (animated) {
        [UIView beginAnimations:@"setEditingAnimation" context:nil];
        [UIView setAnimationDuration:0.3];
    }

    if (editing) {
        /* do your offset and resize here */
    } else {
        /* return to the original here*/
    }

    if (animated)
        [UIView commitAnimations];
}

I don't check if the editing value is the same, but that's just an idea how I did it.

Upvotes: 3

ben
ben

Reputation: 81

To handle swiping as well: (self.editing && !self.showingDeleteConfirmation)

Upvotes: 8

Adam Alexander
Adam Alexander

Reputation: 15180

Thanks to Craig's answer which pointed me in the right direction, I have a solution for this. I reverted my commit which moved the text position based on the editing mode and replaced it with a new solution that sets the entire content view to the correct position any time layoutSubviews is called, which results in an automatic animation when switching to and from edit mode:

- (void)layoutSubviews
{
    CGRect b = [self bounds];
    b.size.height -= 1; // leave room for the separator line
    b.size.width += 30; // allow extra width to slide for editing
    b.origin.x -= (self.editing) ? 0 : 30; // start 30px left unless editing
    [contentView setFrame:b];
    [super layoutSubviews];
}

By doing it this way I was able to remove the setFrame: override found in ABTableViewCell.m because its former logic plus my additions are now found in layoutSubviews.

I set a light grey background on the cells to verify a custom background works properly without allowing us to see behind it as it moves back and forth and it seems to work great.

Thanks again to Craig and anyone else who has looked into this.

GitHub commit for this solution: (link)

Upvotes: 24

Craig Otis
Craig Otis

Reputation: 32054

How are you moving the text around currently? Or more specifically, in which UITableViewCell method are you performing the move?

From my experience, overriding the layoutSubviews method and setting the frame here will automatically be wrapped in an animation.

Eg:

- (void)layoutSubviews {
    if (self.editing) {
        [titleLabel setFrame:CGRectMake(62, 6, 170, 24)];
    }
    else {
        [titleLabel setFrame:CGRectMake(30, 6, 200, 24)];
    }
    [super layoutSubviews];
}

Upvotes: 10

Related Questions