203

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?

Vertexwahn
  • 7,709
  • 6
  • 64
  • 90

17 Answers17

408

Actually, it's very simple:

[_tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];

From the documentation:

Calling this method causes the table view to ask its data source for new cells for the specified sections. The table view animates the insertion of new cells in as it animates the old cells out.

Pang
  • 9,564
  • 146
  • 81
  • 122
dmarnel
  • 4,436
  • 1
  • 15
  • 11
  • 13
    And simply the best answer on this page! – Matthias D Nov 30 '11 at 21:42
  • 47
    This is not entirely correct, because it will only reload the first section. If your table has multiple sections, this will not work – Nosrettap Feb 26 '13 at 21:33
  • 4
    It is possible you will get an exception if no data has changed.. See my [answer](http://stackoverflow.com/a/15328210/666835) – Matej Mar 10 '13 at 22:05
  • 8
    @Nosrettap: if you want to have more or all the sections of your tableView reload, all you have to do is extend your NSIndexSet with all the section indexes, that needs to be refreshed as well – JonEasy Mar 19 '15 at 14:32
  • Swift version: `_tableView.reloadSections(NSIndexSet(index: 0), withRowAnimation: .Fade)` Notably, though, it does only update Section 0, which is the default, first section. – kbpontius Mar 03 '16 at 09:26
  • Swift 5 version: `tableView.reloadSections(IndexSet(integer: 0), with: .fade)` – Genki Aug 21 '20 at 21:45
297

You might want to use:

Objective-C

[UIView transitionWithView: self.tableView
                  duration: 0.35f
                   options: UIViewAnimationOptionTransitionCrossDissolve
                animations: ^(void)
 {
      [self.tableView reloadData];
 }
                completion: nil];

Swift

UIView.transitionWithView(tableView,
                          duration: 0.35,
                          options: .TransitionCrossDissolve,
                          animations:
{ () -> Void in
    self.tableView.reloadData()
},
                          completion: nil);

Swift 3, 4 & 5

UIView.transition(with: tableView,
                  duration: 0.35,
                  options: .transitionCrossDissolve,
                  animations: { self.tableView.reloadData() }) // left out the unnecessary syntax in the completion block and the optional completion parameter

No hassles. :D

You can also use any of the UIViewAnimationOptionTransitions you want for cooler effects:

  • transitionNone
  • transitionFlipFromLeft
  • transitionFlipFromRight
  • transitionCurlUp
  • transitionCurlDown
  • transitionCrossDissolve
  • transitionFlipFromTop
  • transitionFlipFromBottom
PhillipJacobs
  • 2,337
  • 1
  • 16
  • 32
Kenn Cal
  • 3,659
  • 2
  • 17
  • 18
  • 3
    This is useful if the start/end state of your tableview will be very different (and it would be complex to calculate the sections and rows to add/remove), but you wand something less jarring that the non-animated reload. – Ben Packard Nov 27 '12 at 20:54
  • 1
    This isn't as nice an animation as reloading the tableview using it's built in methods, but unlike the higher rated method mentioned here, this one works when you have multiple sections. – Mark Bridges Dec 23 '13 at 10:35
  • 1
    Wow after trying all the rest this is defiantly perfect for me – Lucas Goossen Jan 11 '14 at 02:49
  • 1
    @MarkBridges the higher rated answer does work with multiple sections :) - `[_tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withRowAnimation:UITableViewRowAnimationFade];` – lobianco Feb 04 '14 at 00:16
  • 3
    @Anthony It doesn't work if the end state has more/less sections than the starting state. Then you'd have to manually track which sections got added/deleted, which is quite a hassle. – nikolovski Jul 08 '14 at 11:57
  • simple and fast way to implement a general purpose animation, if there's no real need for customisation then this one should be used. – Felipe Jul 21 '14 at 13:54
  • This is such an easy solution! Zero Fuss!! For swift UIView.transitionWithView(tableView, duration: 0.35, options: .TransitionCrossDissolve, animations: { () -> Void in self.tableView.reloadData() }, completion: nil) – DogCoffee Apr 05 '15 at 10:57
  • @DogCoffee: Thanks bro, I added your implementation to the answer. – Kenn Cal Apr 06 '15 at 09:50
  • Definitely the best answer. Reloading sections is so bothersome especially when the sections to reload are not continuous or you have to delete a section in the middle so the section numbers to reload change – funct7 Jun 01 '15 at 07:22
  • It works for me fine even when there are more or less sections than in the previous state. Very easy solution! – adamsfamily Apr 09 '17 at 08:36
  • this worked better than reloading the section (above solution) – coolcool1994 May 24 '18 at 00:13
84

Have more freedom using CATransition class.

It isn't limited to fading, but can do movements as well..


For example:

(don't forget to import QuartzCore)

CATransition *transition = [CATransition animation];
transition.type = kCATransitionPush;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.fillMode = kCAFillModeForwards;
transition.duration = 0.5;
transition.subtype = kCATransitionFromBottom;

[[self.tableView layer] addAnimation:transition forKey:@"UITableViewReloadDataAnimationKey"];

Change the type to match your needs, like kCATransitionFade etc.

Implementation in Swift:

let transition = CATransition()
transition.type = kCATransitionPush
transition.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionEaseInEaseOut)
transition.fillMode = kCAFillModeForwards
transition.duration = 0.5
transition.subtype = kCATransitionFromTop
self.tableView.layer.addAnimation(transition, forKey: "UITableViewReloadDataAnimationKey")
// Update your data source here
self.tableView.reloadData()

Reference for CATransition

Swift 5:

let transition = CATransition()
transition.type = CATransitionType.push
transition.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
transition.fillMode = CAMediaTimingFillMode.forwards
transition.duration = 0.5
transition.subtype = CATransitionSubtype.fromTop
self.tableView.layer.add(transition, forKey: "UITableViewReloadDataAnimationKey")
// Update your data source here
self.tableView.reloadData()
Sergio
  • 1,610
  • 14
  • 28
Matej
  • 9,548
  • 8
  • 49
  • 66
  • 1
    this animates the table as a whole, instead of single rows/sections. – Agos Nov 29 '13 at 15:17
  • 1
    @Agos It still answers the question. A question "How to reload only one row" has quite a different answer, such as applying the animation to the `layer` property of a `UITableViewCell`. – Matej Nov 29 '13 at 20:04
  • @matejkramny I expected the table to animate just the different rows (as referenced in the question), but this method pushes all the table at once. Maybe I am missing something? – Agos Nov 29 '13 at 20:10
  • @Agos hmm no it is supposed to do that. It can't do only half the table as QuartzCore modifies the view directly. You can try getting the cells from the table view, and then applying this animation to each though (but not sure it would work) – Matej Nov 29 '13 at 20:20
  • @matejkramny Can you help me implement this solution? Do I run this block of code once only to setup the animation? How do I trigger the animation on say, [MyTable ReloadData]? – Matthys Du Toit Jul 18 '14 at 14:21
  • @MatthysDuToit hmm i don't recall right now, but try putting this into your -viewDidLoad. Any subsequent table reloads should use that animation.. If that doesnt work try running it just before refreshing ur data (eg button click). Hope this helps! If u like to chat, email matej@matej.me thanks :) – Matej Jul 18 '14 at 14:26
  • 3
    worked like a charm with kCATransitionFade Thanks! :) – quarezz Oct 15 '15 at 11:49
  • Superb solution dude.....by the way its giving a shocking animation with the tableview....Nice One!!!..:) – onCompletion Jan 09 '16 at 13:37
61

I believe you can just update your data structure, then:

[tableView beginUpdates];
[tableView deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES];
[tableView insertSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES];
[tableView endUpdates];

Also, the "withRowAnimation" is not exactly a boolean, but an animation style:

UITableViewRowAnimationFade,
UITableViewRowAnimationRight,
UITableViewRowAnimationLeft,
UITableViewRowAnimationTop,
UITableViewRowAnimationBottom,
UITableViewRowAnimationNone,
UITableViewRowAnimationMiddle
Tiago Fael Matos
  • 2,077
  • 4
  • 20
  • 34
26

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];
kwahn
  • 2,118
  • 2
  • 21
  • 17
diadyne
  • 4,038
  • 36
  • 28
  • 3
    Good point about the section calculations, but I had only one section and your code had an off-by-one error. I had to change the first line of your code to NSRange range = NSMakeRange(0, myTableView.numberOfSections); – Danyal Aytekin Jul 13 '12 at 12:41
19

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.

Forge
  • 6,538
  • 6
  • 44
  • 64
iKenndac
  • 18,730
  • 3
  • 35
  • 51
18

I can't comment on the top answer, but a swift implementation would be:

self.tableView.reloadSections([0], with: UITableViewRowAnimation.fade)

you could include as many sections as you want to update in the first argument for reloadSections.

Other animations available from the docs: https://developer.apple.com/reference/uikit/uitableviewrowanimation

fade The inserted or deleted row or rows fade into or out of the table view.

right The inserted row or rows slide in from the right; the deleted row or rows slide out to the right.

left The inserted row or rows slide in from the left; the deleted row or rows slide out to the left.

top The inserted row or rows slide in from the top; the deleted row or rows slide out toward the top.

bottom The inserted row or rows slide in from the bottom; the deleted row or rows slide out toward the bottom.

case none The inserted or deleted rows use the default animations.

middle The table view attempts to keep the old and new cells centered in the space they did or will occupy. Available in iPhone 3.2.

automatic The table view chooses an appropriate animation style for you. (Introduced in iOS 5.0.)

Christopher Larsen
  • 1,375
  • 15
  • 22
17

Swift 4 version for @dmarnel answer:

tableView.reloadSections(IndexSet(integer: 0), with: .automatic)
chengsam
  • 7,315
  • 6
  • 30
  • 38
10

For Swift 4

tableView.reloadSections([0], with: UITableView.RowAnimation.fade)
Claus
  • 5,662
  • 10
  • 77
  • 118
9

Swift Implementation:

let range = NSMakeRange(0, self.tableView!.numberOfSections())
let indexSet = NSIndexSet(indexesInRange: range)
self.tableView!.reloadSections(indexSet, withRowAnimation: UITableViewRowAnimation.Automatic)
Michael Peterson
  • 10,383
  • 3
  • 54
  • 51
3

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)
})
JPetric
  • 3,838
  • 28
  • 26
1

In my case, I wanted to add 10 more rows into the tableview (for a "show more results" type of functionality) and I did the following:

  NSInteger tempNumber = self.numberOfRows;
  self.numberOfRows += 10;
  NSMutableArray *arrayOfIndexPaths = [[NSMutableArray alloc] init];
  for (NSInteger i = tempNumber; i < self.numberOfRows; i++) {
    [arrayOfIndexPaths addObject:[NSIndexPath indexPathForRow:i inSection:0]];
  }
  [self.tableView beginUpdates];
  [self.tableView insertRowsAtIndexPaths:arrayOfIndexPaths withRowAnimation:UITableViewRowAnimationTop];
  [self.tableView endUpdates];

In most cases, instead of "self.numberOfRows", you would usually use the count of the array of objects for the tableview. So to make sure this solution works well for you, "arrayOfIndexPaths" needs to be an accurate array of the index paths of the rows being inserted. If the row exists for any of this index paths, the code might crash, so you should use the method "reloadRowsAtIndexPaths:withRowAnimation:" for those index pathds to avoid crashing

Lucas Chwe
  • 2,578
  • 27
  • 17
1

If you want to add your own custom animations to UITableView cells, use

[theTableView reloadData];
[theTableView layoutSubviews];
NSArray* visibleViews = [theTableView visibleCells];

to get an array of visible cells. Then add any custom animation to each cell.

Check out this gist I posted for a smooth custom cell animation. https://gist.github.com/floprr/1b7a58e4a18449d962bd

flopr
  • 450
  • 4
  • 23
1
CATransition *animation = [CATransition animation];
animation.duration = .3;
[animation setType:kCATransitionPush];
[animation setSubtype:kCATransitionFromLeft];
[animation setTimingFunction:[CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut]];
[animation setDuration:.3];

[[_elementTableView layer] addAnimation:animation forKey:@"UITableViewReloadDataAnimationKey"];

[tableView reloadData];
Pang
  • 9,564
  • 146
  • 81
  • 122
1

Animating without reloadData() in Swift can be done like this (as of version 2.2):

tableview.beginUpdates()
var indexPathsToDeleteForAnimation: [NSIndexPath] = []
var numOfCellsToRemove = ArrayOfItemsToRemove ?? 0

// Do your work here
while numOfCellsToRemove > 0 {
    // ...or here, if you need to add/remove the same amount of objects to/from somewhere
    indexPathsToDeleteForAnimation.append(NSIndexPath(forRow: selectedCellIndex+numOfCellsToRemove, inSection: 0))
    numOfCellsToRemove -= 1
}
tableview.deleteRowsAtIndexPaths(indexPathsToDeleteForAnimation, withRowAnimation: UITableViewRowAnimation.Right)
tableview.endUpdates()

in case you need to call reloadData() after the animation ends, you can embrace the changes in CATransaction like this:

CATransaction.begin()
CATransaction.setCompletionBlock({() in self.tableview.reloadData() })
tableview.beginUpdates()
var indexPathsToDeleteForAnimation: [NSIndexPath] = []
var numOfCellsToRemove = ArrayOfItemsToRemove.count ?? 0

// Do your work here
while numOfCellsToRemove > 0 {
     // ...or here, if you need to add/remove the same amount of objects to/from somewhere
     indexPathsToDeleteForAnimation.append(NSIndexPath(forRow: selectedCellIndex+numOfCellsToRemove, inSection: 0))
     numOfCellsToRemove -= 1
}
tableview.deleteRowsAtIndexPaths(indexPathsToDeleteForAnimation, withRowAnimation: UITableViewRowAnimation.Right)
tableview.endUpdates()
CATransaction.commit()

The logic is shown for the case when you delete rows, but the same idea works also for adding rows. You can also change animation to UITableViewRowAnimation.Left to make it neat, or choose from the list of other available animations.

Vitalii
  • 4,267
  • 1
  • 40
  • 45
1

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

guard let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows else {
    return
}

tableView.layoutIfNeeded()
tableView.reloadRows(at: indexPathsForVisibleRows, with: .automatic)
Kqtr
  • 5,824
  • 3
  • 25
  • 32
frO
  • 11
  • 3
-1

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.

craft
  • 2,017
  • 1
  • 21
  • 30