Search code examples
iosswiftuiviewuiviewanimationcatransaction

Why is UIView animation not working when starting right after view is added?


The animation of a UIView should start right after the view is added to the superview:

class myView: UIView {
    override func didMoveToSuperview() {
        super.didMoveToSuperview()

        UIView.animate(
            withDuration: duration,
            delay: 0,
            options: .curveLinear,
            animations: {
                CATransaction.begin()
                CATransaction.setAnimationDuration(duration)
                self.layer.colors = [color1, color2]
                CATransaction.commit()
        },
            completion: nil
        )
    }
}

However, the result is that the animation is already over when the view becomes visible in the superview. UIView.animate does not animate but sets self.layer.colors immediately, maybe because the view is not yet visible when didMoveToSuperview is called.

How can I make the animation start normally?


Solution

  • Animating gradients using core animation can be done by creating a CABasicAnimation and adding it to your CAGradientLayer. You don't need to wrap this in a UIView animation block and you can do this from viewDidMoveToSuperview provided that you add your UIView subclass instance to the view hierarchy sometime after your root view has been added to a UIWindow. For example, in a playground one can write:

    import UIKit
    import PlaygroundSupport
    
    class MyView: UIView
    {
        var duration = 20.0
    
        var fromColors = [UIColor.orange.cgColor, UIColor.blue.cgColor]
        var toColors = [UIColor.red.cgColor, UIColor.green.cgColor]
    
        override func didMoveToSuperview() {
            super.didMoveToSuperview()
    
            let gradientLayer = CAGradientLayer()
            gradientLayer.frame = bounds
            layer.addSublayer(gradientLayer)
    
            let animation = CABasicAnimation(keyPath: "colors")
            animation.fromValue = fromColors
            animation.toValue = toColors
            animation.duration = duration
            gradientLayer.add(animation, forKey: nil)
    
            gradientLayer.colors = toColors
        }
    }
    
    class ViewController: UIViewController
    {
        override func viewDidAppear(_ animated: Bool) {
            super.viewDidAppear(animated)
            let myView = MyView(frame: view.bounds)
            view.addSubview(myView)
        }
    }
    
    PlaygroundPage.current.liveView = ViewController()
    

    and see a 20 second animation from vertical orange to blue gradient animate to a vertical red to green gradient.