如何让UITableViewCell首次显示时仅执行一次动画?
Hey there! The problem with your current code is that tableView(_:willDisplay:forRowAt:) gets called every time a cell enters the screen—including when you scroll back up to cells that were already shown. That means your animation runs over and over again, which isn't what you want.
Here are a few clean ways to make the animation fire only once when the cell first appears:
Option 1: Add a Flag to Your Custom Cell (Most Straightforward)
If you're using a custom UITableViewCell subclass, just add a boolean property to track if the animation has already run:
class YourCustomCell: UITableViewCell { // Track if the animation has completed var hasAnimated = false }
Then update your willDisplay method to check this flag before animating:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { // Cast to your custom cell type and check the flag guard let customCell = cell as? YourCustomCell, !customCell.hasAnimated else { return } // Set initial hidden state customCell.alpha = 0 let transform = CATransform3DTranslate(CATransform3DIdentity, 0, 200, 0) customCell.layer.transform = transform // Animate to visible state UIView.animate( withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut ) { customCell.alpha = 1 customCell.layer.transform = CATransform3DIdentity // Reset to original transform } completion: { _ in // Mark the cell as animated once the animation finishes customCell.hasAnimated = true } }
Option 2: Use Associated Objects (No Custom Cell Needed)
If you don't want to create a custom cell subclass, you can use Objective-C runtime associated objects to add a flag to any UITableViewCell:
import ObjectiveC // Create a unique key for the associated object private var hasAnimatedKey: UInt8 = 0 extension UITableViewCell { var hasAnimated: Bool { get { return objc_getAssociatedObject(self, &hasAnimatedKey) as? Bool ?? false } set { objc_setAssociatedObject(self, &hasAnimatedKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } }
Then use the same logic in willDisplay:
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard !cell.hasAnimated else { return } cell.alpha = 0 let transform = CATransform3DTranslate(CATransform3DIdentity, 0, 200, 0) cell.layer.transform = transform UIView.animate( withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut ) { cell.alpha = 1 cell.layer.transform = CATransform3DIdentity } completion: { _ in cell.hasAnimated = true } }
Option 3: Track Animated IndexPaths
You can also maintain a set of IndexPaths that have already been animated. This works well if your data doesn't change often:
// Store index paths that have been animated private var animatedIndexPaths = Set<IndexPath>() func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { guard !animatedIndexPaths.contains(indexPath) else { return } cell.alpha = 0 let transform = CATransform3DTranslate(CATransform3DIdentity, 0, 200, 0) cell.layer.transform = transform UIView.animate( withDuration: 0.5, delay: 0, usingSpringWithDamping: 1, initialSpringVelocity: 1, options: .curveEaseOut ) { cell.alpha = 1 cell.layer.transform = CATransform3DIdentity } completion: { _ in self.animatedIndexPaths.insert(indexPath) } }
Note: If your table view data updates (e.g., you add/remove rows), you'll need to clear or update the
animatedIndexPathsset to match the new data.
All these approaches ensure the animation runs only once per cell, even when scrolling back and forth. Pick the one that fits your project setup best!
内容的提问来源于stack exchange,提问作者Uday Babariya

