Have a reloadData for a UITableView animate when changing

Viewed 157459

I have a UITableView that has two modes. When we switch between the modes I have a different number of sections and cells per section. Ideally, it would do some cool animation when the table grows or shrinks.

Here is the code I tried, but it doesn't do anything:

CGContextRef context = UIGraphicsGetCurrentContext(); 
[UIView beginAnimations:nil context:context]; 
[UIView setAnimationCurve:UIViewAnimationCurveEaseInOut]; 
[UIView setAnimationDuration:0.5]; 

[self.tableView reloadData];
[UIView commitAnimations];

Any thoughts on how I could do this?

17 Answers

All of these answers assume that you are using a UITableView with only 1 section.

To accurately handle situations where you have more than 1 section use:

NSRange range = NSMakeRange(0, myTableView.numberOfSections);
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
[myTableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];

(Note: you should make sure that you have more than 0 sections!)

Another thing to note is that you may run into a NSInternalInconsistencyException if you attempt to simultaneously update your data source with this code. If this is the case, you can use logic similar to this:

int sectionNumber = 0; //Note that your section may be different

int nextIndex = [currentItems count]; //starting index of newly added items

[myTableView beginUpdates];

for (NSObject *item in itemsToAdd) {
    //Add the item to the data source
    [currentItems addObject:item];

    //Add the item to the table view
    NSIndexPath *path = [NSIndexPath indexPathForRow:nextIndex++ inSection:sectionNumber];
    [myTableView insertRowsAtIndexPaths:[NSArray arrayWithObject:path] withRowAnimation:UITableViewRowAnimationAutomatic];
}

[myTableView endUpdates];

The way to approach this is to tell the tableView to remove and add rows and sections with the

insertRowsAtIndexPaths:withRowAnimation:,
deleteRowsAtIndexPaths:withRowAnimation:,
insertSections:withRowAnimation: and
deleteSections:withRowAnimation:

methods of UITableView.

When you call these methods, the table will animate in/out the items you requested, then call reloadData on itself so you can update the state after this animation. This part is important - if you animate away everything but don't change the data returned by the table's dataSource, the rows will appear again after the animation completes.

So, your application flow would be:

[self setTableIsInSecondState:YES];

[myTable deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES]];

As long as your table's dataSource methods return the correct new set of sections and rows by checking [self tableIsInSecondState] (or whatever), this will achieve the effect you're looking for.

Swift 4 version for @dmarnel answer:

tableView.reloadSections(IndexSet(integer: 0), with: .automatic)

For Swift 4

tableView.reloadSections([0], with: UITableView.RowAnimation.fade)

To reload all sections, not just one with custom duration.

User duration parameter of UIView.animate to set custom duration.

UIView.animate(withDuration: 0.4, animations: { [weak self] in
    guard let `self` = self else { return }
    let indexSet = IndexSet(integersIn: 0..<self.tableView.numberOfSections)
    self.tableView.reloadSections(indexSet, with: UITableView.RowAnimation.fade)
})

UITableView has a field called 'indexPathsForVisibleRows' that you can use to animate the visible rows using the 'reloadItemsAtIndexPaths' method.

    guard let indexPaths = tableView.indexPathsForVisibleRows
    else { return }
    
    self.tableView.layoutIfNeeded()
    self.tableView.reloadItemsAtIndexPaths(indexPaths, animationStyle: .automatic)

Native UITableView animations in Swift

Insert and delete rows all at once with tableView.performBatchUpdates so they occur simultaneously. Building on the answer from @iKenndac use methods such as:

  • tableView.insertSections
  • tableView.insertRows
  • tableView.deleteSections
  • tableView.deleteRows

Ex:

 tableView.performBatchUpdates({
   tableView.insertSections([0], with: .top)
 })

This inserts a section at the zero position with an animation that loads from the top. This will re-run the cellForRowAt method and check for a new cell at that position. You could reload the entire table view this way with specific animations.

Re: the OP question, a conditional flag would have been needed to show the cells for the alternate table view state.

Related