Swift UICollectionView:使用动画和自定义布局调整单元格大小

Swift UICollectionView:使用动画和自定义布局调整单元格大小,swift,uicollectionviewlayout,Swift,Uicollectionviewlayout,我有一个带有自定义布局和可扩散数据源的集合视图。我想用动画调整单元格的大小 具有自定义布局:它看起来像是动画期间缩放到新大小的单元格内容。只有当单元达到其最终大小时,才会最终重新绘制(请参见下面的动画) 使用库存流布局时子视图在动画中的布局正确:标签保持在顶部,不调整大小,开关也是如此 因此,我假设问题在布局实现中,可以向布局添加哪些内容以允许在动画期间使用正确的布局 说明 本例中使用的自定义布局是,我在代码中使用了自定义布局。我注意到,在某些情况下,始终返回相同的布局属性对象是很重要的,而FS

我有一个带有自定义布局和可扩散数据源的集合视图。我想用动画调整单元格的大小

具有自定义布局:它看起来像是动画期间缩放到新大小的单元格内容。只有当单元达到其最终大小时,才会最终重新绘制(请参见下面的动画)

使用库存流布局时子视图在动画中的布局正确:标签保持在顶部,不调整大小,开关也是如此

因此,我假设问题在布局实现中,可以向布局添加哪些内容以允许在动画期间使用正确的布局

说明

本例中使用的自定义布局是,我在代码中使用了自定义布局。我注意到,在某些情况下,始终返回相同的布局属性对象是很重要的,而
FSQCollectionViewAlignedLayout
不这样做。我修改它不返回副本,结果是一样的

对于单元格(在xib中定义),单元格的
contentMode
及其
contentView
设置为
.top
。单元格的
contentView
包含两个子视图:一个标签(在
layoutSubviews
中布局)和一个开关(带约束布局,但其存在对动画没有影响)

控制器的代码为:

class ViewController: UIViewController, UICollectionViewDelegateFlowLayout {
    enum Section {
        case main
    }
    @IBOutlet weak var collectionView : UICollectionView!
    var layout = FSQCollectionViewAlignedLayout()
    var dataSource : UICollectionViewDiffableDataSource<Section, String>!
    var cellHeight : CGFloat = 100

    override func loadView() {
        super.loadView()
        
        layout.defaultCellSize = CGSize(width: 150, height: 100)
        collectionView.setCollectionViewLayout(layout, animated: false) // uncommented to use the stock flow layout
        
        dataSource = UICollectionViewDiffableDataSource(collectionView: collectionView, cellProvider: { cv, indexPath, item in
            guard let cell = cv.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as? Cell else { return nil }
            cell.label.text = item
            return cell
        })
        
        var snapshot = NSDiffableDataSourceSnapshot<Section, String>()
        snapshot.appendSections([.main])
        snapshot.appendItems(["1", "2"], toSection: .main)
        dataSource.apply(snapshot)
    }
    
    override func viewDidLoad() {
        super.viewDidLoad()
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return CGSize(width: 150, height: cellHeight)
    }
    
    @IBAction func resizeAction(_ sender: Any) {
        if let layout = collectionView.collectionViewLayout as? FSQCollectionViewAlignedLayout {
            UIView.animate(withDuration: 0.25) {
                layout.defaultCellSize.height += 50
                let invalidation = FSQCollectionViewAlignedLayoutInvalidationContext()
                invalidation.invalidateItems(at: [[0, 0], [0, 1]])
                layout.invalidateLayout(with: invalidation)
                self.view.layoutIfNeeded()
            }
        }
        if let layout = collectionView.collectionViewLayout as? UICollectionViewFlowLayout {
            UIView.animate(withDuration: 0.25) {
                self.cellHeight += 50
                let invalidation = UICollectionViewFlowLayoutInvalidationContext()
                invalidation.invalidateItems(at: [[0, 0], [0, 1]])
                layout.invalidateLayout(with: invalidation)
                self.view.layoutIfNeeded()
            }
        }
    }
}

class Cell : UICollectionViewCell {
    @IBOutlet weak var label: UILabel!
    
    override func layoutSubviews() {
        super.layoutSubviews()
        label.frame = CGRect(x: 0, y: 0, width: bounds.width, height: 30)
    }
}
类ViewController:UIViewController、UICollectionViewDelegateFlowLayout{
枚举部分{
主箱
}
@ibvar collectionView:UICollectionView!
var layout=FSQCollectionViewAlignedLayout()
var数据源:UICollectionViewDiffableDataSource!
变量单元格高度:CGFloat=100
重写func loadView(){
super.loadView()
layout.defaultCellSize=CGSize(宽:150,高:100)
collectionView.setCollectionViewLayout(布局,动画:false)//未注释以使用库存流布局
dataSource=UICollectionViewDiffableDataSource(collectionView:collectionView,cellProvider:{cv,indexPath,中的项
guard let cell=cv.dequeueReuseAbleCell(withReuseIdentifier:“cell”,for:indexPath)作为?cell else{return nil}
cell.label.text=项
返回单元
})
var snapshot=NSDiffableDataSourceSnapshot()
snapshot.appendSections([.main])
snapshot.appendItems([“1”,“2”],toSection:.main)
dataSource.apply(快照)
}
重写func viewDidLoad(){
super.viewDidLoad()
}
func collectionView(collectionView:UICollectionView,布局collectionViewLayout:UICollectionViewLayout,sizeForItemAt indexPath:indexPath)->CGSize{
返回CGSize(宽度:150,高度:cellHeight)
}
@iAction func resizeAction(\发送方:任意){
如果让布局=collectionView.collectionViewLayout为?FSQCollectionViewAlignedLayout{
UIView.animate(持续时间:0.25){
layout.defaultCellSize.height+=50
let invalidation=FSQCollectionViewAlignedLayoutInvalidationContext()
无效。无效项(位于:[[0,0],[0,1]])
布局.失效失效(带:失效)
self.view.layoutifneed()
}
}
如果让布局=collectionView.collectionViewLayout为?UICollectionViewFlowLayout{
UIView.animate(持续时间:0.25){
self.cellHeight+=50
let invalidation=UICollectionViewFlowLayoutInvalidationContext()
无效。无效项(位于:[[0,0],[0,1]])
布局.失效失效(带:失效)
self.view.layoutifneed()
}
}
}
}
类单元格:UICollectionViewCell{
@IBVAR标签:UILabel!
覆盖func布局子视图(){
super.layoutSubviews()
label.frame=CGRect(x:0,y:0,宽度:bounds.width,高度:30)
}
}

我在这个问题上花了一张开发者支持票。答案是不支持重新加载单元格时的自定义动画

因此,作为一种解决方法,我最终创建了新的单元实例,并将其添加到视图层次结构中,然后添加自动布局约束,以便它们与正在调整大小的单元完全重叠。然后在这些新添加的单元格上运行动画。动画结束时,将删除这些单元格