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

使用UICollectionViewCompositionalLayout搭配DiffableDataSource如何自定义cell插入删除动画

问题原因

UICollectionViewCompositionalLayout 默认内置了插入删除的淡入淡出动画优先级,会覆盖你重写的initialLayoutAttributesForAppearingItem和finalLayoutAttributesForDisappearingItem的返回值,同时DiffableDataSource应用快照时的动画逻辑会优先遵从layout的内置配置,导致原有自定义动画失效。

实现方案

你可以选择以下两种方案实现自定义动画,可根据业务复杂度选择:

方案1:子类化UICollectionViewCompositionalLayout 兼容原有重写逻辑

该方案可以复用你之前写的自定义属性逻辑,改动量最小:

  • 子类化UICollectionViewCompositionalLayout,保留你原有对initialLayoutAttributesForAppearingItem、finalLayoutAttributesForDisappearingItem的重写逻辑
  • 新增重写shouldInvalidateLayout(forPreferredLayoutAttributes:withOriginalAttributes:)方法返回true,确保你自定义的布局属性不会被系统默认配置覆盖
  • 应用快照时保持apply(snapshot, animatingDifferences: true)的动画参数为true,不要关闭全局动画开关

示例代码:

class CustomCompositionalLayout: UICollectionViewCompositionalLayout {
    // 重写item出现时的初始属性
    override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }
        // 替换为你自定义的动画初始状态,以下为缩放+上移示例
        attributes.alpha = 0
        attributes.transform = CGAffineTransform(scaleX: 0.5, y: 0.5).translatedBy(x: 0, y: 100)
        return attributes
    }
    
    // 重写item消失时的最终属性
    override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        guard let attributes = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)?.copy() as? UICollectionViewLayoutAttributes else {
            return nil
        }
        // 替换为你自定义的动画结束状态,以下为缩放+下移示例
        attributes.alpha = 0
        attributes.transform = CGAffineTransform(scaleX: 0.5, y: 0.5).translatedBy(x: 0, y: -100)
        return attributes
    }
    
    // 新增该重写方法,保证自定义属性生效
    override func shouldInvalidateLayout(forPreferredLayoutAttributes preferredAttributes: UICollectionViewLayoutAttributes, withOriginalAttributes originalAttributes: UICollectionViewLayoutAttributes) -> Bool {
        return true
    }
}

使用时直接用该自定义子类替换原来的UICollectionViewCompositionalLayout初始化即可。

方案2:无需子类化Layout,通过Cell生命周期方法实现

该方案适合简单动画场景,不需要修改原有layout配置:

  • 维护一个集合存储正在执行动画的indexPath,避免cell复用导致的动画重复或错乱
  • 在collectionView(_:willDisplay:forItemAt:)代理方法中设置cell的动画初始状态,再通过UIView动画复位到正常状态
  • 若需要自定义删除动画,可配合collectionView(_:didEndDisplaying:forItemAt:)方法实现

示例代码:

// 全局存储正在执行动画的indexPath,避免复用问题
private var animatingIndexPaths = Set<IndexPath>()

func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
    guard !animatingIndexPaths.contains(indexPath) else { return }
    animatingIndexPaths.insert(indexPath)
    
    // 动画初始状态
    cell.alpha = 0
    cell.transform = CGAffineTransform(translationX: 0, y: 50)
    
    UIView.animate(withDuration: 0.3) {
        // 复位到正常状态
        cell.alpha = 1
        cell.transform = .identity
    } completion: { [weak self] _ in
        self?.animatingIndexPaths.remove(indexPath)
    }
}
注意事项
  • 无论使用哪种方案,应用DiffableDataSource快照时如果设置animatingDifferences: false,所有自定义动画都不会生效
  • 方案2中页面销毁时记得清空animatingIndexPaths,避免内存泄露

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.23 21:24:00