4

I'm new to Core Animation, and I'm trying to update the strokeEnd of a CAShapeLayer, from an old value to a new updated value, based on new data.

So basically I have a drawn CAShapeLayer, and updating it's UIBezierPath, and using CABasicAnimation(forKey: "strokeEnd"), to animate the new strokeEnd by doing this:

layerAnimation = CABasicAnimation(keyPath: "strokeEnd")
layerAnimation.toValue = 1
shapeLayer.strokeEnd = 1
layerAnimation.duration = 0.4
shapeLayer.add(layerAnimation, forKey: animation.identifier)

where animation.identifier is an String Enum value which determine which animation should be used.

So far I've been digging deep in the Apple's Core Animation documentation, and here on stack overflow, for a possible clue, but so far without any luck.

As far as I understand it, by only assigning one value in the animation, core animation would figure out the path itself, but so far with no luck. I've also tried to calculate the difference between the old and new top Y value, and use the byValue, with and without fromValue or toValue.

The updated path is drawn, but unfortunately the animation is not shown with the strokeEndupdate.

Any help, hints, or advice, would be received with great gratitude.

Cheers!


Edit to add more details based on @matt's feedback: TLDR; updating path, thought I should animate with toValue.

I'm storing an array containing drawn CAShapeLayer that follows a UIBezierPath. The path is determined based on data that can be of three different values, and should go from (x: i, y: 0) -> (x: i, y: n). where x is a fixed position, and y is the end point of the path, in it's given array[i] column.

If there is an update to the data in the i'th position, I re-calculate the UIBezierPath, which is done in newPath(x:y:h:) -> UIBezierPath

        if let previous: CAShapeLayer = previousColumns[i] {
            shapeLayer = previous
            bezierPath = UIBezierPath()
            bezierPath = newPath(x: xPoint(i), y: newY, viewHeight: height)
            shapeLayer.path = bezierPath.cgPath
            animate(shapeLayer: shapeLayer, animation: .updateAnimation)
        } else { draw new CAShapeLayer with UIBezierPath }

The animate(shapeLayer:animation:) contains the code first mentioned, as it was my understanding that before an animation can take place, either a CABasicAnimation or CAKeyFrameAnimation should be added to the given CAShapeLayer.

T. Hyldgaard
  • 328
  • 8
  • 16
  • I have also been thinking about changing the `CABasicAnimation` to `CAKeyFrameAnimation`, as I would get access to more detailed value manipulation, as far as I understand? – T. Hyldgaard Mar 20 '18 at 16:44
  • Sorry, but what's the problem exactly? Is it that the change in `strokeEnd` is not animated? In that case, why don't you just set it directly: `myLayer.strokeEnd = newValue`? That is animated automatically. – matt Mar 20 '18 at 17:15
  • 1
    Or is the problem that you are trying to change the `path` and the `strokeEnd` both at once? That would make no sense to me. If you want to animate a change in `path`, that is possible but tricky. It really would help if you would show what you are _really_ doing, not just some excerpt showing what you _think_ is relevant. – matt Mar 20 '18 at 17:16
  • thanks for the feedback @matt, I've added more details to the answer. Basically I'm trying to update the path, and animate that update. Which can go up and down, or just stay in the same position, based on the new data. – T. Hyldgaard Mar 21 '18 at 07:54

1 Answers1

5

Okay so after a bit of guidance, @matt made me realise, that it wasn't the strokeEnd that should be updated, but rather the path. So by tweaking my code a bit, I realised that by setting the UIBezierPath of the new column to be drawn, to the old one at first, and then creating the new path, I could animate the path with a CABasicAnimation for the path, by using the animations from value as the old path, and the toValue as the new value.

        if let previous: CAShapeLayer = previousColumns[i], let oldPath: CGPath = previous.path {
            shapeLayer = previous
            bezierPath = UIBezierPath(cgPath: oldPath)
            shapeLayer.path = bezierPath.cgPath
            let newBezierPath = newPath(x: x, y: newY, viewHeight: height)
            animate(shapeLayer: shapeLayer, animation: .updateAnimation, newBezierPath: newBezierPath.cgPath)
        }

And then update the animation block:

        layerAnimation = CABasicAnimation(keyPath: "path")
        layerAnimation.fromValue = shapeLayer.path
        layerAnimation.toValue = newBezierPath
        shapeLayer.path = newBezierPath

Again, I couldn't have figured this out, without @matt's helpful tips, so thank you @matt! :)

T. Hyldgaard
  • 328
  • 8
  • 16