Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
436 views
in Technique[技术] by (71.8m points)

swift - UICollectionView: Resizing cells with animations and custom layouts

I have a collection view with a custom layout and a diffable datasource. I would like to resize the cells with an animation.

With a custom layout: it looks like the content of the cells scaled to the new size during the animation. Only when the cells reaches its final size, it is finally redrawn (see animation below)

With a stock flow layout the subviews are laid out correctly during the animation: the label remains at the top, no resizing and same for the switch.

So I would assume that the issue is in the layout implementation, what could be added to the layout allow a correct layout during the animation?

Description

The custom layout used in this example is FSQCollectionViewAlignedLayout, I also got it with the custom I used in my code. I noticed that for some cases, it is important to always return the same layout attributes objects, which FSQCollectionViewAlignedLayout doesn't do. I modified it to not return copies, and the result is the same.

For the cells (defined in the xib), the contentMode of the cell and its contentView are set to .top. The cell's contentView contains 2 subviews: a label (laid out in layoutSubviews) and a switch (laid out with constraints, but its presence has no effect on the animation).

The code of the controller is:

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)
    }
}

When resizing the content of the cell is scaled

question from:https://stackoverflow.com/questions/65917752/uicollectionview-resizing-cells-with-animations-and-custom-layouts

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

I spent a developer support ticket on this question. The answer was that custom animations when reloading cells are not supported.

So as a workaround, I ended up creating new cells instances that I add to the view hierarchy, then add autolayout constraints so that they overlap exactly the cells being resized. The animation is then run on those newly added cells. At the end of the animation, these cells are removed.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...