Search code examples
swiftcashapelayerquartz-corecaanimation

CAShapeLayer shadowPath animation. How to prevent shadow fill on animation completion?


I'm creating a random UIBezierPath, setting the path to a CAShapeLayer's .path property, and then animating the CAShapeLayer's stroke like so:

    // Get a path
    let path = createBezierPath()

    // Shape layer properties
    let color = randomCGColor() // Color of path and shadow
    let lineWidth = CGFloat(arc4random_uniform(50) + 11) // Width of line and shadow radius
    shapeLayer.path = path.CGPath
    shapeLayer.lineJoin = kCALineJoinRound
    shapeLayer.lineCap = kCALineCapRound
    shapeLayer.strokeColor = color
    shapeLayer.lineWidth = lineWidth
    shapeLayer.fillColor = UIColor.clearColor().CGColor
    // Shape layer shadow properties
    shapeLayer.shadowPath = path.CGPath
    shapeLayer.shadowColor = color
    shapeLayer.shadowOffset = CGSizeZero
    shapeLayer.shadowRadius = lineWidth
    shapeLayer.shadowOpacity = 1.0

    // Add shape layer
    self.layer.addSublayer(shapeLayer)

    // Stroke path animation
    let pathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
    pathAnimation.fromValue = 0.0
    pathAnimation.toValue = 1.0

    // Shadow path animation
    let shadowPathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "shadowPath")
    shadowPathAnimation.fromValue = 0.0
    shadowPathAnimation.toValue = path.CGPath

    // Animation group
    let animationGroup = CAAnimationGroup()
    animationGroup.duration = 5.0
    animationGroup.animations = [pathAnimation, shadowPathAnimation]

    shapeLayer.addAnimation(animationGroup, forKey: "allMyAnimations")

The issue is, once the animation completes the shadow is applied to the entire shape. I'd like the shadow to just be applied to the CAShapeLayer's stroke. How can I prevent the shadow from applying to the entire shape after the animation completes?

Here's an example of the issue I'm referring to:

enter image description here


Solution

  • I was able to solve the issue, with the help of this answer, by creating a CALayer for the shadow and adding the CAShapeLayer as a sublayer:

        let path = createBezierPath() // Get a path
        let color = randomCGColor() // Color of path and shadow
        let lineWidth = CGFloat(arc4random_uniform(50) + 11) // Width of line and shadow radius
    
        // Shape layer properties
        shapeLayer.path = path.CGPath
        shapeLayer.lineJoin = kCALineJoinRound
        shapeLayer.lineCap = kCALineCapRound
        shapeLayer.strokeColor = color
        shapeLayer.lineWidth = lineWidth
        shapeLayer.fillColor = UIColor.clearColor().CGColor
    
        // Create shadow layer
        let shadowLayer = CALayer()
        shadowLayer.frame = self.bounds
        shadowLayer.shadowColor = color
        shadowLayer.shadowOffset = CGSizeZero
        shadowLayer.shadowRadius = lineWidth
        shadowLayer.shadowOpacity = 1.0
        shadowLayer.backgroundColor = UIColor.clearColor().CGColor
        shadowLayer.insertSublayer(shapeLayer, atIndex: 0)
    
        // Add shadow layer
        self.layer.addSublayer(shadowLayer)
    
        // Stroke path animation
        let pathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
        pathAnimation.fromValue = 0.0
        pathAnimation.toValue = 1.0
        pathAnimation.duration = 5.0
        shapeLayer.addAnimation(pathAnimation, forKey: "strokeAnimation")
    
        // Shadow path animation
        let shadowPathAnimation: CABasicAnimation = CABasicAnimation(keyPath: "shadowPath")
        shadowPathAnimation.fromValue = 0.0
        shadowPathAnimation.toValue = path.CGPath
        shadowLayer.addAnimation(shadowPathAnimation, forKey: "shadowAnimation")
    

    Example of the result:

    enter image description here