Weird UITableView insert/delete row animation

2019-03-22 19:12发布

问题:

I'm seeing a weird effect when inserting/deleting a UITableViewCell in a UITableView with animation (UITableViewRowAnimationTop).

The animation glitch happens when the cell to insert is much bigger than the cell above.

This video shows the glitch in the simulator, yellow cell appears suddenly out of no where when it's supposed to slide from top.

Here is the Xcode project from the video.

Bellow is the cell insertion/animation code.

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 2 + self.thirdCellVisible;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:NO];

    if (indexPath.row == 1)
    {
        if (self.thirdCellVisible)
        {
            self.thirdCellVisible = !self.thirdCellVisible;
            [tableView deleteRowsAtIndexPaths:@[self.thirdCellIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];
        }
        else
        {
            self.thirdCellVisible = !self.thirdCellVisible;
            [tableView insertRowsAtIndexPaths:@[self.thirdCellIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];
        }
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row == self.thirdCellIndexPath.row)
    {
        return 100.0f;
    }

    return 44.0f;
}

回答1:

You will have to call

[tableView beginUpdates] 

and

[tableView endUpdates] 

before and after calling insert/delete methods as below :

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 2 + self.thirdCellVisible;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:NO];

    if (indexPath.row == 1)
    {
        if (self.thirdCellVisible)
        {
            self.thirdCellVisible = !self.thirdCellVisible;
            [self.tableView beginUpdates];

            [tableView deleteRowsAtIndexPaths:@[self.thirdCellIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];

            [self.tableView endUpdates];

        }
        else
        {
            self.thirdCellVisible = !self.thirdCellVisible;
            [self.tableView beginUpdates];

            [tableView insertRowsAtIndexPaths:@[self.thirdCellIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];

            [self.tableView endUpdates];

        }
    }
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row == self.thirdCellIndexPath.row)
    {
        return 100.0f;
    }

    return 44.0f;
}


回答2:

According to Apple here:

To animate a batch insertion, deletion, and reloading of rows and sections, call the corresponding methods within an animation block defined by successive calls to beginUpdates and endUpdates. If you don’t call the insertion, deletion, and reloading methods within this block, row and section indexes may be invalid. Calls to beginUpdates and endUpdates can be nested; all indexes are treated as if there were only the outer update block.

At the conclusion of a block—that is, after endUpdates returns—the table view queries its data source and delegate as usual for row and section data. Thus the collection objects backing the table view should be updated to reflect the new or removed rows or sections.

You will have to call [tableView beginUpdates] and [tableView endUpdates] before and after calling insert/delete methods respectively.

An example is provided by Apple on the above link.

Note: If your array and table view items are out of sync, without the begin/end calls an exception will be thrown.

Suggestion: Try first withRowAnimation:UITableViewRowAnimationAutomatic



回答3:

Here is the link of video of my working code: https://dl.dropboxusercontent.com/u/30316681/480.mov

Below is my working code:

@interface ViewController ()

@property (nonatomic, readwrite) BOOL thirdCellVisible;
@property (nonatomic, strong) NSIndexPath *thirdCellIndexPath;

@end

@implementation ViewController

- (void)viewDidLoad
{
    [super viewDidLoad];
    self.thirdCellIndexPath = [NSIndexPath indexPathForRow:2 inSection:0];
}

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
    return 2 + self.thirdCellVisible;
}

- (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
{
    if (indexPath.row == self.thirdCellIndexPath.row)
    {
        return 100.0f;
    }

    return 44.0f;
}

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
    [tableView deselectRowAtIndexPath:indexPath animated:NO];

    if (indexPath.row == 1)
    {
        if (self.thirdCellVisible)
        {
            self.thirdCellVisible = !self.thirdCellVisible;
            [tableView deleteRowsAtIndexPaths:@[self.thirdCellIndexPath]
                         withRowAnimation:UITableViewRowAnimationTop];
        }
        else
        {
            self.thirdCellVisible = !self.thirdCellVisible;
            [tableView insertRowsAtIndexPaths:@[self.thirdCellIndexPath]
                         withRowAnimation:UITableViewRowAnimationTop];
        }
    }
}

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
{
    static NSString *cellId = @"id";

    UITableViewCell *cell = [tableView dequeueReusableHeaderFooterViewWithIdentifier:cellId];

    if(cell == nil)
    {
        cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellId];
    }

    NSString *cellTitle = [NSString stringWithFormat:@"Cell %ld", (long)indexPath.row];

    cell.textLabel.text = cellTitle;

    if(indexPath.row == 2)
        cell.backgroundColor = [UIColor yellowColor];

    else
        cell.backgroundColor = [UIColor clearColor];

    return cell;
}

@end


回答4:

Use [tableView beginUpdates] and [tableView endUpdates] as follow

  - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath

{
    [tableView deselectRowAtIndexPath:indexPath animated:NO];

    if (indexPath.row == 1)
    {
        if (self.thirdCellVisible)
        {
            self.thirdCellVisible = !self.thirdCellVisible;

            [self.tableView beginUpdates];
            [tableView deleteRowsAtIndexPaths:@[self.thirdCellIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];
             [self.tableView endUpdates];

        }
        else
        {
            self.thirdCellVisible = !self.thirdCellVisible;

            [self.tableView beginUpdates];
            [tableView insertRowsAtIndexPaths:@[self.thirdCellIndexPath]
                             withRowAnimation:UITableViewRowAnimationTop];
             [self.tableView endUpdates];

        }
    }
}