2

I'm trying to animate my line shaped CAShapeLayer along a circular path. I created a initial path of my line layer and final path and added them to from and to values of CABasicAnimation.

However animation is not what I was expecting. Im stuck here for last 2 days.

func getAllLayers() -> [CALayer] 

   {

    var layers = []

    let pointerEndPoint = CGPoint(x: xPointOfPointer , y: yPointOfPointer)  // Final end point of pointer
    let radius = 5

    let pointerFinalPath = UIBezierPath()
    pointerFinalPath.addArc(withCenter: circleCenter, radius: radius, startAngle: 0, endAngle:2 * CGFloat.pi, clockwise: false)
    pointerFinalPath.move(to: circleCenter)
    pointerFinalPath.addLine(to: endPoint)
    pointerFinalPath.close()

    let pointerPathLayer = CAShapeLayer()
    pointerPathLayer.path = pointerFinalPath.cgPath
    pointerPathLayer.lineWidth = 2
    pointerPathLayer.fillColor = UIColor.black.cgColor
    pointerPathLayer.strokeColor = UIColor.black.cgColor
    layers.append(pointerPathLayer)

    let pointerInitialBezierPath = UIBezierPath()
    pointerInitialBezierPath.addArc(withCenter: circleCenter, radius: radius,startAngle: 0 , endAngle: 2 * CGFloat.pi , clockwise: false)
    pointerInitialBezierPath.move(to: circleCenter)
    pointerInitialBezierPath.addLine(to: CGPoint(x: circleCenter.x - 50 , y: centerY))
    pointerInitialBezierPath.close()


    let pointerInitialCGPath = pointerInitialBezierPath.cgPath
    let pointerFinalCGPath = pointerFinalPath.cgPath


                    // Pointer Animation
    let pointerAnimation = CABasicAnimation(keyPath: #keyPath(CAShapeLayer.path))
     pointerAnimation.fromValue = pointerInitialCGPath
     pointerAnimation.toValue = pointerFinalCGPath
      pointerAnimation.duration = 0.5
      pointerAnimation.isCumulative = true
      pointerPathLayer.add(pointerAnimation, forKey: "Animation")

return layers
}

Searched For solution in Stack over flow but not able to find my mistake. I want my animation to move like a needle of a clock.

My Animation

Notice that the needle length is changing as animation proceeds. I want it to stay at constant length and only want my angle of needle to change. I would greatly appreciate any help.

EDIT 1: If I try transform.rotation animation im getting needle rotation but it happen around a wrong center point.

      // Pointer Animation

            let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
            rotateAnimation.fromValue = 0.0
            rotateAnimation.toValue = angleOfActualValueinRad
            rotateAnimation.duration = 2.0
            pointerPathLayer.add(rotateAnimation, forKey: "Animation")

Roatation Animation

EDIT 2 : Use this link to find my github project . Animation Issue

Jaffer Sheriff
  • 1,444
  • 13
  • 33
  • what if you'll change `anchorPoint` of `pointerPathLayer ` and add rotation animation instead? – Mike H Jul 23 '19 at 06:49
  • I post answer with rotation code. – girish_pro Jul 23 '19 at 07:03
  • @girish_pro Question Updated – Jaffer Sheriff Jul 23 '19 at 07:14
  • You need to setup anchor point. check my answer. add following line. pointerPathLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5) – girish_pro Jul 23 '19 at 07:34
  • check you Github. I post issue with code suggestion. – girish_pro Jul 23 '19 at 09:33
  • 1
    People Thanks for all of ur help and time. Issue here is I was not setting frame for CAShapeLayer .This practise came as result of using constraints with UIViews. So as @girish_pro suggested i fixed it by properly doing dirty work of computing frame for each CAShapelayer and applying from angle value and to angle value in my CABasicAnimation. – Jaffer Sheriff Jul 25 '19 at 06:54

3 Answers3

1

You can't achieve speed meter animation using UIBezierPath. You need to transform Line in whatever angle you want. Please refer following code for rotation which I made just for solving your problem.

let circleCenter = CGPoint(x: 150.0, y: 150.0)

let startPoint = CGPoint(x: 100, y: 150.0)
    // Start Path
    let pointerStartPath = UIBezierPath()
    pointerStartPath.move(to: circleCenter)
    pointerStartPath.addLine(to: startPoint)
    pointerStartPath.close()

    let pointerPathLayer = CAShapeLayer()
    pointerPathLayer.frame = CGRect(x: 0, y:0, width: 300, height: 300)
    pointerPathLayer.path = pointerStartPath.cgPath
    pointerPathLayer.lineWidth = 2
    pointerPathLayer.fillColor = UIColor.green.cgColor
    pointerPathLayer.strokeColor = UIColor.black.cgColor
    self.view.layer.addSublayer(pointerPathLayer)

// New : Set Anchor point
pointerPathLayer.anchorPoint = CGPoint(x: 0.5, y: 0.5)

    let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
    rotateAnimation.fromValue = 0.0
    rotateAnimation.toValue = CGFloat.pi * 2.0
    rotateAnimation.duration = 2.0
    pointerPathLayer.add(rotateAnimation, forKey: "Animation")

Using this code, you can achieve 360º degree rotation.

girish_pro
  • 838
  • 9
  • 18
1

You use wrong transform rotation, try to do smth like this

let rAnimation = CABasicAnimation(keyPath:"transform.rotation.z")
    rAnimation.duration = 1
    rAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    rAnimation.fromValue = degree2radian(90)
    rAnimation.repeatCount = Float.infinity
    rAnimation.toValue = degree2radian(180)
    progressLineLayer.add(rAnimation, forKey:"rotate")

func degree2radian(_ a: CGFloat) -> CGFloat {
    return CGFloat.pi * a / 180
}

look through this link http://sketchytech.blogspot.com/2014/11/swift-how-to-draw-clock-face-using_12.html, may be it will be helpful

UPDATED:

I wrote some code to show how it works

private func animateProgress() {

    // black circle at center

    let blackCirclePath = UIBezierPath()
    blackCirclePath.addArc(withCenter: view.center, radius: 5, startAngle: 0, endAngle: CGFloat.pi * 2, clockwise: true)
    blackCirclePath.fill()
    UIColor.black.setFill()
    blackCirclePath.close()

    let circleLayer = CAShapeLayer()
    circleLayer.path = blackCirclePath.cgPath
    view.layer.addSublayer(circleLayer)

    // all scale gray background layer

    let grayHalfCirclePath = UIBezierPath()
    grayHalfCirclePath.addArc(withCenter: view.center, radius: 50, startAngle: 0, endAngle: CGFloat.pi, clockwise: false)

    let grayHalfCircleLayer = CAShapeLayer()
    grayHalfCircleLayer.frame = view.bounds
    grayHalfCircleLayer.path = grayHalfCirclePath.cgPath
    grayHalfCircleLayer.strokeColor = UIColor.lightGray.cgColor
    grayHalfCircleLayer.fillColor = nil
    grayHalfCircleLayer.lineWidth = 10
    view.layer.addSublayer(grayHalfCircleLayer)

    // animated progress scale

    let progressHalfCirclePath = UIBezierPath()
    progressHalfCirclePath.addArc(withCenter: view.center, radius: 50, startAngle: CGFloat.pi, endAngle: -CGFloat.pi / 2, clockwise: true)

    let progressLayer = CAShapeLayer()
    progressLayer.frame = view.bounds
    progressLayer.path = progressHalfCirclePath.cgPath
    progressLayer.strokeColor = UIColor.orange.cgColor
    progressLayer.fillColor = nil
    progressLayer.lineWidth = 10
    view.layer.addSublayer(progressLayer)

    // animation

    let pAnimation = CABasicAnimation(keyPath: "strokeEnd")
    pAnimation.duration = 1.0
    pAnimation.fromValue = NSNumber(floatLiteral: 0)
    pAnimation.toValue = NSNumber(floatLiteral: 1)
    progressLayer.add(pAnimation, forKey: "strokeEnd")

    // black arrow

    let progressLineLayer = CAShapeLayer()
    progressLineLayer.frame = view.bounds

    let progressLinePath = CGMutablePath()
    progressLinePath.move(to: CGPoint(x: view.center.x, y: view.center.y))
    progressLinePath.addLine(to: CGPoint(x: view.center.x - 30, y: view.center.y - 30))
    progressLineLayer.path = progressLinePath
    progressLineLayer.strokeColor = UIColor.green.cgColor
    progressLineLayer.lineWidth = 1
    progressLineLayer.strokeColor = UIColor.black.cgColor
    view.layer.addSublayer(progressLineLayer)

    let rAnimation = CABasicAnimation(keyPath:"transform.rotation.z")
    rAnimation.duration = 1
    rAnimation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.linear)
    rAnimation.fromValue = degree2radian(-45)
    rAnimation.repeatCount = 1
    rAnimation.toValue = degree2radian(45)
    progressLineLayer.add(rAnimation, forKey:"rotate")
}

func degree2radian(_ a: CGFloat) -> CGFloat {
    return CGFloat.pi * a / 180
}
Alexandr Kolesnik
  • 1,929
  • 1
  • 17
  • 30
0

Try this library ABGuageViewKit

Light weight and customizable with animation.

Hope this helps and happy coding.

Steve Chan
  • 107
  • 1
  • 9