You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在滚动UITableView时强制停止不可见UITableViewCell的动画

问题根因

  • 原方案仅在cellForRowAt出队时清理动画,cell滚出屏幕时没有及时终止绑定的动画,已经启动的动画闭包仍持有cell弱引用,等动画触发时cell已经被复用给其他indexPath,导致动画错乱
  • 动画执行过程中没有校验归属,即使cell被复用,不属于当前indexPath的旧动画仍会执行,影响当前cell的展示

修复方案

按照以下修改即可解决问题:

  1. 给自定义cell增加绑定行标识,用于校验动画归属
  2. 重写cell复用前的清理方法,主动清空旧动画和状态
  3. 实现cell滚出屏幕的代理方法,及时停止动画并标记动画完成
  4. 所有动画闭包增加归属校验,避免复用后执行旧动画

完整修复代码

import UIKit

class TestCell: UITableViewCell {
    var animations: [UIViewPropertyAnimator] = []
    // 存储当前cell绑定的row,用于校验动画归属
    var currentBindRow: Int = -1
    
    override func prepareForReuse() {
        super.prepareForReuse()
        // 复用时立刻停止所有动画,清空状态
        animations.forEach { animator in
            animator.stopAnimation(true)
        }
        animations.removeAll()
        // 重置视图到初始状态,避免旧内容残留
        textLabel?.layer.opacity = 1
        textLabel?.text = nil
        currentBindRow = -1
    }
}

class ViewController: UIViewController {
    @IBOutlet weak var tableView: UITableView!
    // 标记对应row是否已经完成动画(包含滚出屏幕视为完成的情况)
    var animated: [Bool] = Array(repeating: false, count: 100)
    
    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.dataSource = self
        tableView.delegate = self
        tableView.register(TestCell.self, forCellReuseIdentifier: "TestCell")
    }
}

extension ViewController: UITableViewDataSource, UITableViewDelegate {
    func numberOfSections(in tableView: UITableView) -> Int {
        1
    }
    
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        100
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "TestCell", for: indexPath) as! TestCell
        // 绑定当前cell对应的row
        cell.currentBindRow = indexPath.row
        cell.textLabel?.text = "\(indexPath.row)"
        
        if !animated[indexPath.row] {
            // 第一个动画:透明度降到0
            let animator = UIViewPropertyAnimator(duration: 5, curve: .easeIn) { [weak cell] in
                guard let cell = cell, cell.currentBindRow == indexPath.row else { return }
                cell.textLabel?.layer.opacity = 0
            }
            
            animator.addCompletion { [weak cell] completed in
                guard let cell = cell, cell.currentBindRow == indexPath.row else { return }
                cell.textLabel?.layer.opacity = 0
                
                if completed == .end {
                    // 第二个动画:透明度升到1
                    let animator1 = UIViewPropertyAnimator(duration: 5, curve: .easeIn) { [weak cell] in
                        guard let cell = cell, cell.currentBindRow == indexPath.row else { return }
                        cell.textLabel?.layer.opacity = 1
                    }
                    
                    animator1.addCompletion { [weak cell] _ in
                        guard let cell = cell, cell.currentBindRow == indexPath.row else { return }
                        cell.textLabel?.layer.opacity = 1
                    }
                    
                    animator1.startAnimation(afterDelay: 0)
                    cell.animations.append(animator1)
                }
            }
            
            animator.startAnimation(afterDelay: 0)
            cell.animations.append(animator)
        } else {
            // 已经执行过动画,直接设为最终状态
            cell.textLabel?.layer.opacity = 1
        }
        return cell
    }
    
    // cell滚出可视区域时触发
    func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
        guard let cell = cell as? TestCell else { return }
        // 停止当前cell所有动画
        cell.animations.forEach { animator in
            animator.stopAnimation(true)
        }
        cell.animations.removeAll()
        // 标记该indexPath已完成动画,下次进入可视区直接用最终状态
        animated[indexPath.row] = true
    }
}

关键改动说明

  • 新增currentBindRow属性绑定cell当前对应的row,所有动画执行前都会校验归属,避免作用到复用后的cell
  • 重写prepareForReuse方法,cell复用时主动清空所有动画和视图状态,从根源避免旧动画残留
  • 实现didEndDisplaying代理,cell滚出屏幕时立刻停止动画并标记对应row为已完成,符合「滚出即视为动画完成」的需求逻辑
  • 所有动画闭包和回调都增加了归属校验,一旦cell被复用,旧的动画会自动终止执行,不会干扰新的内容

内容的提问来源于stack exchange,提问作者Aswath

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.25 23:06:04