Have a reloadData for a UITableView animate when c

2019-01-07 01:58发布

问题:

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?

回答1:

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.



回答2:

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

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


回答3:

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



回答4:

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


回答5:

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];


回答6:

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.



回答7:

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.)



回答8:

Swift Implementation:

let range = NSMakeRange(0, self.tableView!.numberOfSections())
let indexSet = NSIndexSet(indexesInRange: range)
self.tableView!.reloadSections(indexSet, withRowAnimation: UITableViewRowAnimation.Automatic)


回答9:

Swift 4 version for @dmarnel answer:

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


回答10:

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



回答11:

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



回答12:

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.



回答13:

For Swift 4

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


回答14:

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];


回答15:

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)
})