TableView Cell内UIView动画不一致问题的修复咨询
iOS TableView Cell 动画异常修复方案
问题现象
- 带背景色动画的TableView Cell滑出屏幕再返回后,动画速度异常加快;
- 点击该Cell打开新ViewController,返回原页面后动画完全停止;
- 已尝试在
cellForRowAt中启动动画,未解决问题。
相关代码
TableView代理方法
func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { if let cell = cell as? DirectionSummaryTableViewCell { if let isLive = cell.isLive { if isLive { cell.animateBusStatusBackground() } else { cell.removeAnimation() } } } } func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { guard let cell = tableView.dequeueReusableCell(withIdentifier: DirectionSummaryTableViewCell.identifier, for: indexPath) as? DirectionSummaryTableViewCell else { return UITableViewCell() } cell.configure(with: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].directions, tripDuration: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].tripTime, destinationArrivalTime:viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].reachBy, busDepartureTime: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].directions.routes[0].departureTime, startLocation: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].directions.routes[0].stops[0].name, addFullLabel: true, isLive: viewModel.placeToPlacePossibleDirections[indexPath.section][indexPath.row].responseType == "realtime") return cell }
Cell动画与配置方法
func animateBusStatusBackground() { UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat, .autoreverse, .curveEaseInOut], animations: { [weak self] in if self?.busStatusView.backgroundColor == .red { self?.busStatusView.backgroundColor = .grey6 } else { self?.busStatusView.backgroundColor = .red } }, completion: nil) } func removeAnimation() { self.busStatusView.layer.removeAllAnimations() self.layer.removeAllAnimations() self.layoutIfNeeded() } func configure(with directions: PlaceToPlaceBusDirections, tripDuration: Double, destinationArrivalTime: String, busDepartureTime: String, startLocation: String, addFullLabel: Bool, isLive: Bool) { self.directions = directions self.isLive = isLive self.collectionView.reloadData() self.collectionView.layoutIfNeeded() var formattedTripDuration = "" if tripDuration > 60 { let hrs = Int(tripDuration / 60) formattedTripDuration += String(hrs) + " hr" if hrs > 1 { formattedTripDuration += "s " } else { formattedTripDuration += " " } } formattedTripDuration += String(Int(tripDuration) % 60) self.tripDurationLabel.text = formattedTripDuration + " mins" self.destinationArrivalTimeLabel.text = Date.dateStringFromString(dateString: destinationArrivalTime) if addFullLabel { self.busDeparturePlaceAndTimeLabel.text = ("Leaves at " + Date.dateStringFromString(dateString: busDepartureTime) + " from " + startLocation).maxLength(length: 40) } else { self.busDeparturePlaceAndTimeLabel.text = ("Leaves at " + Date.dateStringFromString(dateString: busDepartureTime)).maxLength(length: 40) } if !isLive { busStatusText.text = "Live" busStatusText.textColor = .white } else { busStatusText.text = "Scheduled" busStatusText.textColor = .grey2 busStatusView.backgroundColor = .grey6 } removeAnimation() }
问题根源
- 动画叠加:Cell复用时未完全清除旧动画,重复启动导致多动画叠加,速度异常;
- 动画销毁:ViewController进入后台时,UIView动画被系统暂停/销毁,返回后未重新触发;
- 逻辑冲突:
configure方法无条件调用removeAnimation(),会清空willDisplay中刚启动的动画。
修复步骤
1. 重构动画方法,避免叠加
修改animateBusStatusBackground,启动前强制清除旧动画,并固定动画起点:
func animateBusStatusBackground() { // 清除已有动画,避免叠加 busStatusView.layer.removeAllAnimations() // 固定初始背景色,确保动画状态一致 busStatusView.backgroundColor = .red UIView.animate(withDuration: 1.0, delay: 0.0, options: [.repeat, .autoreverse, .curveEaseInOut, .allowUserInteraction], animations: { [weak self] in self?.busStatusView.backgroundColor = .grey6 }, completion: nil) }
2. 修正configure的动画清除逻辑
仅在非Live状态下清除动画,避免误删需要运行的动画:
func configure(with directions: PlaceToPlaceBusDirections, tripDuration: Double, destinationArrivalTime: String, busDepartureTime: String, startLocation: String, addFullLabel: Bool, isLive: Bool) { // 省略其他配置逻辑... if !isLive { busStatusText.text = "Live" busStatusText.textColor = .white busStatusView.backgroundColor = .red removeAnimation() // 仅非Live状态清除动画 } else { busStatusText.text = "Scheduled" busStatusText.textColor = .grey2 busStatusView.backgroundColor = .grey6 // Live状态下不调用removeAnimation } }
3. 页面返回时重新触发动画
在TableView所在的ViewController中,重写viewWillAppear,手动刷新可见Cell的动画:
override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) tableView.visibleCells.forEach { cell in guard let customCell = cell as? DirectionSummaryTableViewCell else { return } if customCell.isLive == true { customCell.animateBusStatusBackground() } else { customCell.removeAnimation() } } }
4. 可选:改用Core Animation实现(更稳定)
如果UIView动画仍有问题,替换为Core Animation,避免后台切换导致的动画失效:
func animateBusStatusBackground() { busStatusView.layer.removeAnimation(forKey: "backgroundColorAnimation") let colorAnimation = CABasicAnimation(keyPath: "backgroundColor") colorAnimation.fromValue = UIColor.red.cgColor colorAnimation.toValue = UIColor.grey6.cgColor colorAnimation.duration = 1.0 colorAnimation.repeatCount = .infinity colorAnimation.autoreverses = true colorAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) busStatusView.layer.add(colorAnimation, forKey: "backgroundColorAnimation") busStatusView.backgroundColor = .grey6 // 固定最终状态 } func removeAnimation() { busStatusView.layer.removeAnimation(forKey: "backgroundColorAnimation") busStatusView.backgroundColor = isLive ? .grey6 : .red }
验证效果
- 滑动Cell出屏幕再返回,动画速度保持稳定;
- 跳转页面返回后,动画自动恢复运行;
- Cell复用后动画状态无异常叠加。
内容的提问来源于stack exchange,提问作者Sahil Sahu
相关产品推荐
相关产品推荐

