Search code examples
swiftuikituicollectionviewcelluicollectionviewlayout

Why are my collectionview cells not animating?


I have collection view cells and I'd like to animate all of them at the same time. Even though I'm using UIView.animate the animation does not happen and affine transform happens instantaneously.

enter image description here

class ViewController: UIViewController, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
    
    var collectionview: UICollectionView!
    var moveText: Bool = false

    override func viewDidLoad() {
        super.viewDidLoad()
        
        collectionview = UICollectionView(frame: .zero, collectionViewLayout: UICollectionViewFlowLayout())
        collectionview.translatesAutoresizingMaskIntoConstraints = false
        collectionview.dataSource = self
        collectionview.delegate = self
        collectionview.backgroundColor = .systemBackground
        collectionview.register(MyCell.self, forCellWithReuseIdentifier: "cell")
        view.addSubview(collectionview)
        
        NSLayoutConstraint.activate([
            collectionview.topAnchor.constraint(equalTo: view.topAnchor),
            collectionview.bottomAnchor.constraint(equalTo: view.bottomAnchor),
            collectionview.leadingAnchor.constraint(equalTo: view.leadingAnchor),
            collectionview.trailingAnchor.constraint(equalTo: view.trailingAnchor)
        ])
    }
    
    func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
        10
    }
    
    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
        cell.moveText = moveText
        return cell
    }
    
    func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
        return .init(width: collectionview.frame.width, height: 120)
    }
    
    override func viewDidAppear(_ animated: Bool) {
        super.viewDidAppear(animated)
        
        Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { timer in
            UIView.animate(withDuration: 1, delay: 0) {
                print("Starting animatings")
                self.moveText = !self.moveText
                self.collectionview.reloadData()
            }
        }
    }
    
}

class MyCell: UICollectionViewCell {
    
    let label = UILabel()
    var moveText: Bool = false {
        didSet {
            if moveText == true {
                label.transform = CGAffineTransform(translationX: 50, y: 50)
            } else {
                label.transform = CGAffineTransform(translationX: 0, y: 0)
            }
        }
    }
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        label.text = "mytext"
        label.translatesAutoresizingMaskIntoConstraints = false
        addSubview(label)
        NSLayoutConstraint.activate([
            label.leadingAnchor.constraint(equalTo: leadingAnchor),
            label.topAnchor.constraint(equalTo: topAnchor)
        ])
        backgroundColor = .orange
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

Solution

  • This is because you are animating the wrong thing.

    // adding weak self so the timer will not retain the view controller
    Timer.scheduledTimer(withTimeInterval: 2, repeats: true) { [weak self] timer in
        self?.moveText.toggle() // you can use toggle() on a `Bool`
        self?.collectionview.reloadData()
    }
    

    Instead, you should try to animate setting the value of individual cell

    func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
        let cell = collectionview.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! MyCell
        UIView.animate(withDuration: 1, delay: 0) {
            cell.moveText = self.moveText
        }
        return cell
    }
    

    Also, instead of using CGAffineTransform(translationX: 0, y: 0) you could use .identity

    label.transform = .identity
    

    Worth noting is the fact that reloading collection view every 2 seconds and animating directly in cellForItemAt is very inefficient. You should find another way to pass information to the cell's they should animate the content