我需要给表格视图的行加载动画。当表格重新加载数据时,我希望行从左边一个接一个地进入。我该如何实现这个效果?
我需要给表格视图的行加载动画。当表格重新加载数据时,我希望行从左边一个接一个地进入。我该如何实现这个效果?
Swift 4
添加这个小巧可爱的扩展
extension UITableView {
func reloadWithAnimation() {
self.reloadData()
let tableViewHeight = self.bounds.size.height
let cells = self.visibleCells
var delayCounter = 0
for cell in cells {
cell.transform = CGAffineTransform(translationX: 0, y: tableViewHeight)
}
for cell in cells {
UIView.animate(withDuration: 1.6, delay: 0.08 * Double(delayCounter),usingSpringWithDamping: 0.6, initialSpringVelocity: 0, options: .curveEaseInOut, animations: {
cell.transform = CGAffineTransform.identity
}, completion: nil)
delayCounter += 1
}
}
}
那么,不要使用"tableView.reloadData()",而是使用"tableView.reloadWithAnimation()"
以下是我用Swift 3编写的逐个显示单元格的解决方案。 它的优点在于,它们仅在首次加载时加载,并且仅针对最初显示的单元格(当用户向下滚动时不会运行)。
享受 :)
private var finishedLoadingInitialTableCells = false
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
var lastInitialDisplayableCell = false
//change flag as soon as last displayable cell is being loaded (which will mean table has initially loaded)
if yourTableData.count > 0 && !finishedLoadingInitialTableCells {
if let indexPathsForVisibleRows = tableView.indexPathsForVisibleRows,
let lastIndexPath = indexPathsForVisibleRows.last, lastIndexPath.row == indexPath.row {
lastInitialDisplayableCell = true
}
}
if !finishedLoadingInitialTableCells {
if lastInitialDisplayableCell {
finishedLoadingInitialTableCells = true
}
//animates the cell as it is being displayed for the first time
cell.transform = CGAffineTransform(translationX: 0, y: self.rowHeight/2)
cell.alpha = 0
UIView.animate(withDuration: 0.5, delay: 0.05*Double(indexPath.row), options: [.curveEaseInOut], animations: {
cell.transform = CGAffineTransform(translationX: 0, y: 0)
cell.alpha = 1
}, completion: nil)
}
}
在您的TableView委托方法中,
- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath
使用从Anbu.Karthik答案简化的方法,实现底部向顶部渐隐渐现的翻译动画。
//1. Define the initial state (Before the animation)
cell.transform = CGAffineTransformMakeTranslation(0.f, CELL_HEIGHT);
cell.layer.shadowColor = [[UIColor blackColor]CGColor];
cell.layer.shadowOffset = CGSizeMake(10, 10);
cell.alpha = 0;
//2. Define the final state (After the animation) and commit the animation
[UIView beginAnimations:@"rotation" context:NULL];
[UIView setAnimationDuration:0.5];
cell.transform = CGAffineTransformMakeTranslation(0.f, 0);
cell.alpha = 1;
cell.layer.shadowOffset = CGSizeMake(0, 0);
[UIView commitAnimations];
为了更好的用户体验,建议对于每一行只播放一次动画,直到表视图被释放。
将上述代码放入
if (![self.shownIndexes containsObject:indexPath]) {
[self.shownIndexes addObject:indexPath];
// Your animation code here.
}
------- Swift -----------------------------------------------------------------------------------------------------------------
(Note: This is already in Chinese characters and does not require translation. It is a separator or divider that reads "Swift" in bold.)var shownIndexes : [IndexPath] = []
let CELL_HEIGHT : CGFloat = 40
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
if (shownIndexes.contains(indexPath) == false) {
shownIndexes.append(indexPath)
cell.transform = CGAffineTransform(translationX: 0, y: CELL_HEIGHT)
cell.layer.shadowColor = UIColor.black.cgColor
cell.layer.shadowOffset = CGSize(width: 10, height: 10)
cell.alpha = 0
UIView.beginAnimations("rotation", context: nil)
UIView.setAnimationDuration(0.5)
cell.transform = CGAffineTransform(translationX: 0, y: 0)
cell.alpha = 1
cell.layer.shadowOffset = CGSize(width: 0, height: 0)
UIView.commitAnimations()
}
}
没有任何提供的解决方案能够帮助我,所以我自己想出了一个解决方法。下面是一个通用的类,可以用来链接动画并依次播放它们。它的语法与UIView.animate()相似,一旦调用,它会异步地将动画排队,然后按照添加的顺序依次执行队列:
import UIKit
import Foundation
class ChainedAnimationsQueue {
private var playing = false
private var animations = [(TimeInterval, () -> Void, () -> Void)]()
init() {
}
/// Queue the animated changes to one or more views using the specified duration and an initialization block.
///
/// - Parameters:
/// - duration: The total duration of the animations, measured in seconds. If you specify a negative value or 0, the changes are made without animating them.
/// - initializations: A block object containing the changes to commit to the views to set their initial state. This block takes no parameters and has no return value. This parameter must not be NULL.
/// - animations: A block object containing the changes to commit to the views. This is where you programmatically change any animatable properties of the views in your view hierarchy. This block takes no parameters and has no return value. This parameter must not be NULL.
func queue(withDuration duration: TimeInterval, initializations: @escaping () -> Void, animations: @escaping () -> Void) {
self.animations.append((duration, initializations, animations))
if !playing {
playing = true
DispatchQueue.main.async {
self.next()
}
}
}
private func next() {
if animations.count > 0 {
let animation = animations.removeFirst()
animation.1()
UIView.animate(withDuration: animation.0, animations: animation.2, completion: { finished in
self.next()
})
} else {
playing = false
}
}
}
var animationsQueue = ChainedAnimationsQueue()
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.alpha = 0.0
animationsQueue.queue(withDuration: 0.2, initializations: {
cell.layer.transform = CATransform3DTranslate(CATransform3DIdentity, cell.frame.size.width, 0, 0)
}, animations: {
cell.alpha = 1.0
cell.layer.transform = CATransform3DIdentity
})
}
Swift 4
我为UITableView制作了一个快速的扩展,用于动画显示单元格:
最初的回答:
tableView.reloadData() // To make sure tableView.visibleCells is not empty
tableView.animateCells(
cells: tableView.visibleCells,
duration: 0.3,
delay: 0.5,
dampingRatio: 0.8,
configure: { cell -> (prepare: () -> Void, animate: () -> Void)? in
guard let customCell = cell as? CustomCell else { return nil }
let preparations = {
customCell.iconImageView.alpha = 0
}
let animations = {
customCell.iconImageView.alpha = 1
}
return (preparations, animations)
}, completion: {
print("Cell animations are completed")
})
这个扩展看起来像这样:
extension UITableView {
func animateCells<Cell: UITableViewCell>(cells: [Cell],
duration: TimeInterval,
delay: TimeInterval = 0,
dampingRatio: CGFloat = 0,
configure: @escaping (Cell) -> (prepare: () -> Void, animate: () -> Void)?,
completion: @escaping () -> Void) {
var cellDelay: TimeInterval = 0
var completionCount: Int = 0
for cell in cells {
if let callbacks = configure(cell) {
callbacks.prepare()
let animator = UIViewPropertyAnimator(duration: duration, dampingRatio: dampingRatio)
animator.addAnimations(callbacks.animate)
let completionTime = cellDelay + (duration * TimeInterval(dampingRatio))
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + completionTime) {
completionCount += 1
if completionCount == cells.count {
completion()
}
}
animator.startAnimation(afterDelay: cellDelay)
cellDelay += delay
} else {
completionCount += 1
}
}
}
}
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.alpha = 0
UIView.animate(withDuration: 1) {
cell.alpha = 1.0
}
}
tableView:willDisplayCell:forRowAtIndexPath
方法会在每次 cell 被显示前调用,由于它们同时被查看,这意味着它们在不同的线程中被调用,你不能告诉 iOS SDK 按顺序调用此方法。因此,我认为实现你想要的方式是在每个单元格被显示时设置延迟。
-(void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell*)cell forRowAtIndexPath:(NSIndexPath *)indexPath{
CGFloat delay = indexPath.row * yourSupposedAnimationDuration;
[UIView animateWithDuration:yourSupposedAnimationDuration delay:delay options:UIViewAnimationOptionCurveEaseIn animations:^{
//Your animation code
}completion:^(BOOL finished) {
//Your completion Code
}];
}