-2

I am able to animate the stroke of a curve on time series, but this curve is filled with a color, and the color won't animate, i will see the stroke moving and the fill color is already there.

let shapeLayer = CAShapeLayer()
shapeLayer.fillColor =  curveFillColor!.cgColor
shapeLayer.strokeColor = curveLineColor!.cgColor
shapeLayer.lineWidth = 3.0
shapeLayer.lineJoin = CAShapeLayerLineJoin.round
shapeLayer.lineCap = CAShapeLayerLineCap.round
shapeLayer.strokeStart = 0
shapeLayer.path = path.cgPath
self.layer.addSublayer(shapeLayer)


let animation = CABasicAnimation(keyPath: "strokeEnd")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 4
shapeLayer.add(animation, forKey: "MyAnimation")

enter image description here

I would like both to animate in the same time for this path, so I get a curve that is slowly being filled with color.

the path

  let path = UIBezierPath()
         var point1:CGPoint!
         var point2:CGPoint!
         //var smoothData = self.smooth(alpha: 0.1)



         for i in 0..<curvePoints.count-1
         {
                point1 =  curvePoints[i]
                point2 = curvePoints[i+1]
                point1.y=size!.height-point1.y
                point2.y=size!.height-point2.y

                if( i == 0 ) {path.move(to: point1)}

                path.addLine(to: point2)

          }

EDIT: If i animate using this code, it will not animate from left to right with the stroke, just fill it with color :

   let animation = CABasicAnimation(keyPath: "fillColor")
    animation.fromValue = UIColor.white.cgColor
    animation.toValue = curveFillColor!.cgColor
    animation.duration = 4
    animation.fillMode = .forwards
    animation.isRemovedOnCompletion=false
    shapeLayer.add(animation, forKey: "fillColor")
Curnelious
  • 1
  • 16
  • 76
  • 150
  • I think you're going to need to change the shape of the 'curve' over time to animate the fill. As your curve seems to be a series of straight lines, assuming they map from left to right, you can change the path to animate it. – JonJ Nov 02 '18 at 09:37
  • Investigate https://developer.apple.com/documentation/quartzcore/cadisplaylink to help animate over time. – JonJ Nov 02 '18 at 09:40

1 Answers1

1

This code will allow you to animate over time. Change the bezier path on each call of updatePathsWithAnimation.

weak var displayLink: CADisplayLink?
var startTime: CFTimeInterval!

var shape = CAShapeLayer()

func setup() {
    layer.addSublayer(shape)
    startTime = CACurrentMediaTime()
    displayLink = {
        let _displayLink = CADisplayLink(target: self, selector: #selector(handleDisplayLink(_:)))
        _displayLink.add(to: .current, forMode: .common)
        return _displayLink
    }()
    updatePathsWithAnimation(percentageComplete: 0)
}

@objc func handleDisplayLink(_ displayLink: CADisplayLink) {
    let percent = CGFloat(CACurrentMediaTime() - startTime)
    updatePathsWithAnimation(percentageComplete: percent)
    if percent > 1.0 {
        displayLink.invalidate()
    }
}

func updatePathsWithAnimation(percentageComplete: CGFloat) {
    // Animate the curve
}

So, for example, if the bezier path is made up of 50 points approximating a curve, your updatePathsWithAnimation function would be:

func updatePathsWithAnimation(percentageComplete: CGFloat) {
    var point1 = CGPoint.zero
    var point2 = CGPoint.zero

    // Calculate the number of points to draw, assuming that
    // the points go from left to right.
    // You can also animate scale for a bar chart,
    // the arc of a circle for a pie or donut chart, etc
    // by multiplying the appropriate values by
    // percentageComplete.

    let pointTotal = percentageComplete > 0 ? round(Double(curvePoints.count) / (100.0 - Double(percentageComplete) * 100)) : 0

    // Existing drawing code, but only draws
    // the first percentageComplete % of it...
    // (might need adjusted!)

    for i in for i in 0..< (pointTotal - 1) {
            point1 =  curvePoints[i]
            point2 = curvePoints[i+1]
            point1.y=size!.height-point1.y
            point2.y=size!.height-point2.y

            if( i == 0 ) {path.move(to: point1)}

            // Close the current shape. Need to set height
            // and the original x value
            path.addLine(to: point2)
            path.addLine(to: CGPoint(point2.x, height)
            path.addLine(to: CGPoint(0.0, height)
            path.close()

      }
      // Update the shape
      shape.path = path.cgPath
}

enter image description here

JonJ
  • 1,061
  • 6
  • 8
  • This lets you animate over time, in sync with iOS devices' screen refresh. For example, if you're drawing a bar chart you can have the bars animate vertically to their correct positions. You change the shape of your bezier path on each update to animate it. Use the `percentageComplete` var to calculate the new shape in some fashion. – JonJ Nov 02 '18 at 09:57
  • Thanks, can you please explain(edit) how it fit with my code ? i will appreciate it a lot. – Curnelious Nov 02 '18 at 10:12
  • I've updated the code with an example. *Might* need adjustment. – JonJ Nov 02 '18 at 10:24
  • so every update I draw one line? Or all of them? I am not sure I get it – Curnelious Nov 02 '18 at 16:21
  • It looks like I loop over the same points every update repetitively and going up according to the percentage complete, why the loop? Why not drawing the next point every update and stop when there are no point in the array?? – Curnelious Nov 02 '18 at 16:28
  • You need to update the shape layer each time, so need to keep redrawing the shape. Other option would be to add new paths to the UIBezierPath but redrawing is probably easier. – JonJ Nov 02 '18 at 16:52
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/183022/discussion-between-jonj-and-curnelious). – JonJ Nov 02 '18 at 16:53
  • Ohh so you redraw because the screen will only update the change. Thanks !! :) – Curnelious Nov 02 '18 at 16:56