更改 UITableView 的 reloadData 时如何添加动画效果?

203

我有一个UITableView, 有两种模式。在这两种模式之间切换时,每个部分和每个部分的单元格数量都不同。理想情况下,当表格扩大或缩小时,它会执行一些很酷的动画。

这是我尝试过的代码,但是它没有产生任何效果:

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

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

有什么想法我怎样能做到这一点吗?

17个回答

408

事实上,它非常简单:

[_tableView reloadSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:UITableViewRowAnimationFade];
根据文档:
调用此方法会导致表视图向其数据源请求指定部分的新单元格。 表视图在动画过程中插入新单元格,同时使旧单元格移出。

13
这是这个页面上最好的答案! - Matthias D
47
这并不完全正确,因为它只会重新加载第一节。如果您的表格有多个节,这将无法起作用。 - Nosrettap
4
如果没有数据更改,可能会出现异常。请参见我的回答 - Matej
8
如果您想要重新加载tableView的更多或全部部分,您只需要将NSIndexSet扩展到所有需要刷新的部分索引即可。 - JonEasy
Swift版本:_tableView.reloadSections(NSIndexSet(index:0),withRowAnimation:.Fade)。值得注意的是,它只更新第0节,也就是默认的第一节。 - kbpontius
Swift 5版本:tableView.reloadSections(IndexSet(integer: 0), with: .fade) - Genki

297

您可能想要使用:

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

没有麻烦。 :D

您还可以使用任何您想要的UIViewAnimationOptionTransitions,以获得更酷的效果:

  • transitionNone
  • transitionFlipFromLeft
  • transitionFlipFromRight
  • transitionCurlUp
  • transitionCurlDown
  • transitionCrossDissolve
  • transitionFlipFromTop
  • transitionFlipFromBottom

3
如果你的表格视图的起始/结束状态差异非常大(计算需要添加/删除的节和行比较复杂),那么这将非常有用,但你希望某种东西比非动画式的重新加载更加平缓。 - Ben Packard
1
这个动画不如使用内置方法重新加载tableview那么好,但与此处提到的更高评级的方法不同,当你有多个部分时,这个方法是有效的。 - Mark Bridges
1
尝试了所有其他的选择后,这绝对是最适合我的。 - Lucas Goossen
1
@MarkBridges,更高评分的答案确实适用于多个部分 :) - [_tableView reloadSections:[NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, sectionCount)] withRowAnimation:UITableViewRowAnimationFade]; - lobianco
3
如果结束状态的节比起开始状态要多/少,那么这种方法是行不通的。这时你需要手动跟踪哪些部分被添加或删除,这会相当麻烦。 - nikolovski
显示剩余6条评论

84

使用CATransition类可以获得更多自由。

它不仅限于淡入淡出,还可以进行移动。


例如:

(别忘了导入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"];

type更改为符合您需求的类型,例如kCATransitionFade等。

在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()

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

1
这将整个表格作为一个整体进行动画处理,而不是单独的行/部分。 - Agos
1
@Agos 它仍然回答了这个问题。一个问题“如何重新加载一行”有一个非常不同的答案,比如将动画应用于UITableViewCelllayer属性。 - Matej
@matejkramny 我原本期望表格只会动画显示不同的行(如问题中所提到的),但是这种方法会一次性推动整个表格。也许我漏掉了什么? - Agos
@Agos 嗯,不是这样的。由于QuartzCore直接修改视图,因此它无法仅处理表格的一半。您可以尝试从表视图中获取单元格,然后对每个单元格应用此动画(但不确定是否有效)。 - Matej
3
使用 kCATransitionFade 效果很好。 - quarezz
显示剩余3条评论

61

我认为你只需要更新你的数据结构,然后:

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

另外,“withRowAnimation”并不是一个布尔值,而是一种动画样式:

UITableViewRowAnimationFade,
UITableViewRowAnimationRight,
UITableViewRowAnimationLeft,
UITableViewRowAnimationTop,
UITableViewRowAnimationBottom,
UITableViewRowAnimationNone,
UITableViewRowAnimationMiddle

1
这是一些简单的东西,但是直接去StackOverflow获取你需要的代码片段比查阅文档要容易得多。谢谢! - Jasper Blues

26

所有这些答案都假定您正在使用只有1个 section 的 UITableView。

如果要准确处理具有多个 section 的情况,请使用:

NSRange range = NSMakeRange(0, myTableView.numberOfSections);
NSIndexSet *indexSet = [NSIndexSet indexSetWithIndexesInRange:range];
[myTableView reloadSections:indexSet withRowAnimation:UITableViewRowAnimationAutomatic];
(Note: 请确保你有超过0个 section!)
另一个需要注意的是,如果你试图使用这段代码同时更新数据源,可能会遇到 NSInternalInconsistencyException 异常。如果出现这种情况,你可以使用类似于以下逻辑的方式:
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];

3
你提到的关于章节计算的观点很好,但是我只有一个章节,并且你的代码存在一处差一错误。我不得不将你的代码第一行更改为NSRange range = NSMakeRange(0, myTableView.numberOfSections);。 - Danyal Aytekin

19
处理这个问题的方法是通过使用UITableView的insertRowsAtIndexPaths:withRowAnimation:deleteRowsAtIndexPaths:withRowAnimation:insertSections:withRowAnimation:deleteSections:withRowAnimation:方法来告诉tableView删除和添加行和部分。
当您调用这些方法时,表格将动画显示/隐藏您请求的项目,然后在自身上调用reloadData,以便您可以在此动画之后更新状态。 这部分很重要-如果您动画消失了所有内容,但未更改表格的数据源返回的数据,则行再次出现在动画完成后。
因此,应用程序流程如下: [self setTableIsInSecondState:YES]; [myTable deleteSections:[NSIndexSet indexSetWithIndex:0] withRowAnimation:YES]]; 只要您的表格的dataSource方法通过检查 [self tableIsInSecondState](或其他方式)返回正确的新部分和行集,这将实现您想要的效果。

18

我不能对置顶答案发表评论,但快速实施的方法是:

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

你可以在reloadSections的第一个参数中包含任意数量的要更新的部分。

其他可用于动画的选项来自文档: https://developer.apple.com/reference/uikit/uitableviewrowanimation

fade 插入或删除的行会淡入或淡出表视图。

right 插入的行从右侧滑入,删除的行向右侧滑出。

left 插入的行从左侧滑入,删除的行向左侧滑出。

top 插入的行从顶部滑入,删除的行向顶部滑出。

bottom 插入的行从底部滑入,删除的行向底部滑出。

case none 插入或删除的行使用默认的动画效果。

middle 表视图尝试将新旧单元格保持居中。 适用于iPhone 3.2。

automatic 表视图为您选择适当的动画样式。(iOS 5.0引入)


1
Swift 4.2 中使用:self.tableView.reloadSections([0], with: UITableView.RowAnimation.fade) - Reefwing

17

Swift 4版本适用于@dmarnel的答案:

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

10

For Swift 4

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

9

Swift 实现:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接