使用带有流布局的基本 UICollectionView 时奇怪的删除项目动画

Weird delete items animation when using basic UICollectionView with Flow Layout

我运行在使用最简单的UICollectionView和UICollectionViewFlowLayout时遇到了问题。

集合本身工作正常,但是当删除单元格时,动画出现问题。

这是演示问题的代码示例:

class Cell: UICollectionViewCell { }

class MyViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    var items = Array(repeating: [UIColor.red, .blue, .yellow, .cyan, .magenta, .green], count: 100).flatMap { [=10=] }
    
    lazy var collectionView: UICollectionView = {
        let collectionViewLayout = UICollectionViewFlowLayout()
        collectionViewLayout.scrollDirection = .vertical
        collectionViewLayout.minimumLineSpacing = 10
        collectionViewLayout.minimumInteritemSpacing = 10
        
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.register(Cell.self, forCellWithReuseIdentifier: "Cell")
        collectionView.delegate = self
        collectionView.dataSource = self
        return collectionView
    }()

    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            .init(item: collectionView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0),
            .init(item: collectionView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
            .init(item: collectionView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0),
            .init(item: collectionView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0),
        ])
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        items.count
    }
        
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
        cell.backgroundColor = items[indexPath.item]
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        CGSize(width: UIScreen.main.bounds.width, height: 300)
    }
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        items.remove(at: indexPath.item)
        collectionView.deleteItems(at: [indexPath])
    }
}

问题本身看起来像这样(慢动作):

您可以注意到下一个单元格如何在动画持续期间消失,并在动画结束时出现。

问题:我做错了什么?这是集合的一个非常基本的用法,我对这些问题感到困惑。

看来你的阵列有问题。 它是动态创建的,与 UICollectionView 的实现有冲突。

尝试用以下静态数组替换您的数组。

    var items = [UIColor.red, .blue, .yellow, .cyan, .magenta, .green, .red, .blue, .yellow, .cyan, .magenta, .green]

这是更改后的样子。

您必须继承 UICollectionViewFlowLayout 并覆盖这两个方法 initialLayoutAttributesForAppearingItem finalLayoutAttributesForDisappearingItem

通过更改“attributes”属性,您将获得不同的效果,只需更改第 59 行的动画持续时间并检查它们即可。

import UIKit

class Cell: UICollectionViewCell { }

class ViewController: UIViewController ,UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout {
    
    var items = Array(repeating: [UIColor.red, .blue, .yellow, .cyan, .magenta, .green], count: 100).flatMap { [=10=] }
    
    lazy var collectionView: UICollectionView = {
        
        let collectionViewLayout = MyCollectionLayout()
        collectionViewLayout.scrollDirection = .vertical
        collectionViewLayout.minimumLineSpacing = 10
        collectionViewLayout.minimumInteritemSpacing = 10
        
        let collectionView = UICollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
        collectionView.translatesAutoresizingMaskIntoConstraints = false
        collectionView.register(Cell.self, forCellWithReuseIdentifier: "Cell")
        collectionView.delegate = self
        collectionView.dataSource = self
        return collectionView
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        view.addSubview(collectionView)
        NSLayoutConstraint.activate([
            .init(item: collectionView, attribute: .leading, relatedBy: .equal, toItem: view, attribute: .leading, multiplier: 1, constant: 0),
            .init(item: collectionView, attribute: .trailing, relatedBy: .equal, toItem: view, attribute: .trailing, multiplier: 1, constant: 0),
            .init(item: collectionView, attribute: .bottom, relatedBy: .equal, toItem: view, attribute: .bottom, multiplier: 1, constant: 0),
            .init(item: collectionView, attribute: .top, relatedBy: .equal, toItem: view, attribute: .top, multiplier: 1, constant: 0),
        ])
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        items.count
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "Cell", for: indexPath) as! Cell
        cell.backgroundColor = items[indexPath.item]
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        CGSize(width: UIScreen.main.bounds.width, height: 300)
    }
    
    
    func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
        
        UIView.animate(withDuration: 0.2) {
            self.collectionView.performBatchUpdates({
                
                self.items.remove(at: indexPath.item)
                collectionView.deleteItems(at: [indexPath])
            }, completion: nil)
        }
        
    }
}



class  MyCollectionLayout : UICollectionViewFlowLayout {
    var rowOffset : CGFloat = 0.0
    var deleteIndexPaths : Array<IndexPath> = []
    var insertIndexPaths : Array<IndexPath> = []
    
    override func prepare(forCollectionViewUpdates updateItems: [UICollectionViewUpdateItem]) {
        super.prepare(forCollectionViewUpdates: updateItems)
        
        self.deleteIndexPaths =  Array<IndexPath>()
        self.insertIndexPaths =  Array<IndexPath>()
        
        for updateItem in updateItems {
            if (updateItem.updateAction == .delete){
                self.deleteIndexPaths.append(updateItem.indexPathBeforeUpdate!)
                
            }else if (updateItem.updateAction == .insert){
                self.insertIndexPaths.append(updateItem.indexPathAfterUpdate!)
            }
        }
    }
    
    
    override func initialLayoutAttributesForAppearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        var attributes = super.initialLayoutAttributesForAppearingItem(at: itemIndexPath)
        if(attributes == nil){
            attributes = self.layoutAttributesForItem(at: itemIndexPath)
        }
        
        attributes?.alpha = 1
        return attributes
    }
    
    override func finalizeCollectionViewUpdates()
    {
        super.finalizeCollectionViewUpdates()
        self.deleteIndexPaths = [];
        self.insertIndexPaths = [];
    }
    
    
    override func finalLayoutAttributesForDisappearingItem(at itemIndexPath: IndexPath) -> UICollectionViewLayoutAttributes? {
        
        var attributes = super.finalLayoutAttributesForDisappearingItem(at: itemIndexPath)
        if ((attributes == nil)){
            attributes = self.layoutAttributesForItem(at: itemIndexPath)
        }
        attributes?.transform = CGAffineTransform(scaleX: 0, y: 0)
        attributes?.zIndex = -1
        attributes?.alpha = 1
        
        return attributes
    }
}

从其余的答案中,这个故障的原因变得很清楚 - 如果一些新单元格不在屏幕上,删除动画就会出现问题。

我通过向动画块添加布局失效来解决问题:

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    collectionView.performBatchUpdates({
        self.items.remove(at: indexPath.item)
        collectionView.deleteItems(at: [indexPath])
        collectionView.collectionViewLayout.invalidateLayout()
    }, completion: nil)
}