Search code examples
iosswiftios14

Animating CALayer is not working on iOS 14


I have a problem with animating CALayer after updating to Xcode12 and iOS14 - animations that used to work on iOS13 are not working on iOS14. Here is the code for animations:

private func animateCellPress() {
    let lightShadowAnimation = CABasicAnimation(keyPath: "shadowOffset")
    lightShadowAnimation.fromValue = CGSize(width: -self.shadowRadius, height: -self.shadowRadius)
    lightShadowAnimation.toValue = CGSize(width: self.shadowRadius, height: self.shadowRadius)
    lightShadowAnimation.fillMode = .forwards;
    lightShadowAnimation.isRemovedOnCompletion = false;
    lightShadowAnimation.duration = self.animationDuration
    self.lightShadow.add(lightShadowAnimation, forKey: lightShadowAnimation.keyPath)

    let darkShadowAnimation = CABasicAnimation(keyPath: "shadowOffset")
    darkShadowAnimation.fromValue = CGSize(width: self.shadowRadius, height: self.shadowRadius)
    darkShadowAnimation.toValue = CGSize(width: -self.shadowRadius, height: -self.shadowRadius)
    darkShadowAnimation.fillMode = .forwards;
    darkShadowAnimation.isRemovedOnCompletion = false;
    darkShadowAnimation.duration = self.animationDuration
    self.darkShadow.add(darkShadowAnimation, forKey: darkShadowAnimation.keyPath)
}

lightShadow and darkShadow are stored as a properties and here is the initialisation code for them:

private func initializeDarkShadow() {
    darkShadow = CALayer()
    darkShadow.frame = bounds
    darkShadow.backgroundColor = backgroundColor?.cgColor
    darkShadow.cornerRadius = cornerRadius
    darkShadow.shadowOffset = CGSize(width: shadowRadius, height: shadowRadius)
    darkShadow.shadowOpacity = 1
    darkShadow.shadowRadius = shadowRadius
    darkShadow.shadowColor = Asset.Colors.darkShadow.color.cgColor
    let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
    darkShadow.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
    layer.insertSublayer(darkShadow, at: 0)
}

private func initializeLightShadow() {
    lightShadow = CALayer()
    lightShadow.frame = bounds
    lightShadow.backgroundColor = backgroundColor?.cgColor
    lightShadow.cornerRadius = cornerRadius
    lightShadow.shadowOffset = CGSize(width: -shadowRadius, height: -shadowRadius)
    lightShadow.shadowOpacity = 1
    lightShadow.shadowRadius = shadowRadius
    lightShadow.shadowColor = Asset.Colors.lightShadow.color.cgColor
    let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
    lightShadow.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
    layer.insertSublayer(lightShadow, at: 0)
}

Any ideas on what's wrong and how can I fix it? Also I am not getting any compiler errors or warnings. Console log is empty as well.


Solution

  • This works. I think each time you are calling your animate layoutSubviews is being called thus removing the layer and the animation. You could either just update the frame there or check if the layer has already been added. If that is not the issue then there is something wrong with you press or touch function. Create a project with a single view and copy and paste this into your ViewController file.

    import UIKit
    
    class TableViewCell : UITableViewCell{
        let darkShadowColor = UIColor.green
        let lightShadowColor = UIColor.red
        let animationDuration : Double = 2
        
        var darkShadow = CALayer()
        var lightShadow = CALayer()
        let cornerRadius : CGFloat = 4
        let shadowRadius : CGFloat = 3
        
        override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
            super.init(style: style, reuseIdentifier: reuseIdentifier)
            initializeDarkShadow()
            initializeLightShadow()
        }
        
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            initializeDarkShadow()
            initializeLightShadow()
        }
        
        private func initializeDarkShadow() {
            darkShadow = CALayer()
            darkShadow.frame = bounds
            darkShadow.backgroundColor = backgroundColor?.cgColor
            darkShadow.cornerRadius = cornerRadius
            darkShadow.shadowOffset = CGSize(width: shadowRadius, height: shadowRadius)
            darkShadow.shadowOpacity = 1
            darkShadow.shadowRadius = shadowRadius
            darkShadow.shadowColor = darkShadowColor.cgColor
            let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
            darkShadow.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
            layer.insertSublayer(darkShadow, at: 0)
        }
    
        private func initializeLightShadow() {
            lightShadow = CALayer()
            lightShadow.frame = bounds
            lightShadow.backgroundColor = backgroundColor?.cgColor
            lightShadow.cornerRadius = cornerRadius
            lightShadow.shadowOffset = CGSize(width: -shadowRadius, height: -shadowRadius)
            lightShadow.shadowOpacity = 1
            lightShadow.shadowRadius = shadowRadius
            lightShadow.shadowColor = lightShadowColor.cgColor
            let rect = CGRect(origin: CGPoint(x: 0, y: 0), size: bounds.size)
            lightShadow.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
            layer.insertSublayer(lightShadow, at: 0)
        }
        
        func animateCellPress() {
            print("called")
            let lightShadowAnimation = CABasicAnimation(keyPath: "shadowOffset")
            lightShadowAnimation.fromValue = CGSize(width: -self.shadowRadius, height: -self.shadowRadius)
            lightShadowAnimation.toValue = CGSize(width: self.shadowRadius, height: self.shadowRadius)
            lightShadowAnimation.beginTime = CACurrentMediaTime()
            lightShadowAnimation.fillMode = .both;
            lightShadowAnimation.isRemovedOnCompletion = false;
            lightShadowAnimation.duration = self.animationDuration
            self.lightShadow.add(lightShadowAnimation, forKey: nil)
    
    
            let darkShadowAnimation = CABasicAnimation(keyPath: "shadowOffset")
            darkShadowAnimation.fromValue = CGSize(width: self.shadowRadius, height: self.shadowRadius)
            darkShadowAnimation.toValue = CGSize(width: -self.shadowRadius, height: -self.shadowRadius)
            darkShadowAnimation.beginTime = CACurrentMediaTime()
            darkShadowAnimation.fillMode = .both;
            darkShadowAnimation.isRemovedOnCompletion = false;
            darkShadowAnimation.duration = self.animationDuration
            self.darkShadow.add(darkShadowAnimation, forKey: nil)
        }
        
        override func layoutSubviews() {
            super.layoutSubviews()
            self.lightShadow.frame = self.bounds
            self.darkShadow.frame = self.bounds
        }
    }
    
    class ViewController: UIViewController {
    
        let identifier = "someCellID"
        lazy var tableView : UITableView = {
            let tv = UITableView(frame: self.view.bounds)
            tv.delegate = self
            tv.dataSource = self
            tv.autoresizingMask = [.flexibleWidth,.flexibleHeight]
            tv.register(TableViewCell.self, forCellReuseIdentifier: identifier)
            return tv
        }()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            // Do any additional setup after loading the view.
            self.view.addSubview(tableView)
        }
    }
    
    extension ViewController : UITableViewDelegate,UITableViewDataSource{
        func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
            return 20
        }
        
        func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
            if let cell = tableView.dequeueReusableCell(withIdentifier: identifier, for: indexPath) as? TableViewCell{
                cell.selectionStyle = .none
                return cell
            }
            print("error")
            return UITableViewCell()
        }
        
        
        
        func tableView(_ tableView: UITableView, didHighlightRowAt indexPath: IndexPath) {
            if let cell = tableView.cellForRow(at: indexPath) as? TableViewCell{
                cell.animateCellPress()
            }
        }
        
        func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
            return 50
        }
    }