动画自定义 UITableViewCell 时的计时问题

Timing issue when animating custom UITableViewCell

我的应用程序架构有问题...

我有一个 UITableView 装满了自定义 UITableViewCells。 当然,我使用了出列,所以只生成了 8-10 个单元格实例。

继续我的问题...我已将主题功能添加到我的应用程序中。 当用户长时间触摸主 UINavigationBar 时,会在应用程序范围内发布一条通知,通知每个 viewController 更新他们的 UI。

当 viewController 托管带有自定义单元格的 tableView 收到通知时,它会调用 tableView.reloadData()

效果很好,因为我已经实现了func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath)

willDisplay 中,我在我的自定义 tableViewCell 上调用了一个方法,该方法使用 UIViewAnimation 对单元格进行适当的颜色更改。

看起来像这样:

func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    if let newsFeedItemCell = cell as? NewsFeedItemCell {
        if (self.nightModeEnabled) {
            newsFeedItemCell.transitionToNightMode()
        } else {
            newsFeedItemCell.transitionFromNightMode()
        }
    }
}

在自定义单元格实现中,这些方法如下所示:

func transitionToNightMode() {
        UIView.animate(withDuration: 1, animations: {
            self.backgroundColor = UIColor.black
            self.titleTextView.backgroundColor = UIColor.black
            self.titleTextView.textColor = UIColor.white
        })
}

func transitionFromNightMode() {
    UIView.animate(withDuration: 1, animations: {
        self.backgroundColor = UIColor.white
        self.titleTextView.backgroundColor = UIColor.white
        self.titleTextView.textColor = UIColor.black
    })
}

这很好用...这是问题所在:

在滚动 tableView 时,任何不在屏幕上的单元格 在滚动到屏幕上时,它们的颜色 update/animation 代码称为 ,这会导致不和谐的用户体验。

我当然理解为什么会这样,因为 willDisplay 仅在单元格显示时被调用。

我想不出避免这种情况的优雅方法。

我很高兴屏幕上 的单元格 正在为用户体验带来愉悦的动画效果,但是,对于屏幕外的单元格,我宁愿它们 跳过 动画。


可能的解决方案(虽然不雅):

保留对 cellForRow 创建的 8-10 个单元格中的每一个的引用,并检查它们是否在屏幕外,是否立即设置了它们的状态。

但是,我不喜欢保留对每个单元格的引用的想法。

有什么想法吗?

我会尝试以下方法。

我不会在 transition(To/From)NightMode 中使用 UIView.animate,而是创建一个可以执行相同动画的 UIViewPropertyAnimator 对象。我会保留对该对象的引用,然后准备重用,如果动画仍然是 运行,我会简单地完成该动画并重置状态。所以像这样:

fileprivate var transitionAnimator: UIViewPropertyAnimator?

fileprivate func finishAnimatorAnimation() {
    // if there is a transitionAnimator, immediately finishes it
    if let animator = transitionAnimator {
        animator.stopAnimation(false)
        animator.finishAnimation(at: .end)
        transitionAnimator = nil
    }
}

override func prepareForReuse() {
    super.prepareForReuse()
    finishAnimatorAnimation()
}

func transitionToNightMode() {
    transitionAnimator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut, animations: {
        self.backgroundColor = UIColor.black
        self.titleTextView.backgroundColor = UIColor.black
        self.titleTextView.textColor = UIColor.white
    })
    transitionAnimator?.startAnimation()
}

func transitionFromNightMode() {
    transitionAnimator = UIViewPropertyAnimator(duration: 1, curve: .easeInOut, animations: {
        self.backgroundColor = UIColor.white
        self.titleTextView.backgroundColor = UIColor.white
        self.titleTextView.textColor = UIColor.black
    })
    transitionAnimator?.startAnimation()
}

我不会使用 reloadData 对此进行动画处理,因为您的 tableview 数据模型实际上并没有改变。

相反,我会给 UITableView 一个函数,如下所示:

class myClass : UITableViewController {
    ....

    func transitionToNightMode() {
            for visible in visibleCells {
            UIView.animate(withDuration: 1, animations: {
                visible.backgroundColor = UIColor.black
                visible.titleTextView.backgroundColor = UIColor.black
                visible.titleTextView.textColor = UIColor.white
            })
            }
    }
}

然后在您的 willDisplay 或 cellForItemAt 中,设置正确的无动画外观。