2
votes

I'm trying to animate a UIView(a square) to move along a UIBezierPath using a CAKeyframeAnimation. The square pauses at two points along the bezier path, both points being right before the path begins to arc.This is my code for the UIBezierPath and Animation:

 func createTrack() -> UIBezierPath {
    let path = UIBezierPath()
    path.move(to: CGPoint(x: layerView.frame.size.width/2, y: 0.0))
    path.addLine(to: CGPoint(x: layerView.frame.size.width - 100.0, y: 0.0))
    path.addArc(withCenter: CGPoint(x: layerView.frame.size.width - 100.0,y: layerView.frame.height/2), radius: layerView.frame.size.height/2, startAngle: CGFloat(270).toRadians(), endAngle: CGFloat(90).toRadians(), clockwise: true)
    path.addLine(to: CGPoint(x:  100.0, y: layerView.frame.size.height))
    path.addArc(withCenter: CGPoint(x: 100.0,y: layerView.frame.height/2), radius: layerView.frame.size.height/2, startAngle: CGFloat(90).toRadians(), endAngle: CGFloat(270).toRadians(), clockwise: true)

    path.close()

    return path
}

@IBAction func onAnimatePath(_ sender: Any) {
    let square = UIView()
    square.frame = CGRect(x: 55, y: 300, width: 20, height: 20)
    square.backgroundColor = UIColor.red

    layerView.addSubview(square)

    let animation = CAKeyframeAnimation(keyPath: "position")

    animation.path = trackPath.cgPath

    animation.rotationMode = kCAAnimationRotateAuto
    animation.repeatCount = Float.infinity
    animation.duration = 60

    square.layer.add(animation, forKey: "animate position along path")
}

layerView is just a UIView. Any ideas on why this happens and how I can fix this?

1

1 Answers

4
votes

The path you are using consists of 5 segments (two arcs and three lines (including the one when you close the path)) and the animation spends the same time on each of the segments. If this path if too narrow, these lines segments will have no length and the square will appear still for 12 seconds in each of them.

5 segments of the path

You probably want to use a "paced" calculation mode to achieve a constant velocity

animation.calculationMode = kCAAnimationPaced

This way, the red square will move at a constant pace – no matter how long each segment of the shape is.

This alone will give you the result you are after, but there's more you can do to simplify the path.

The addArc(...) method is rather smart and will add a line from the current point to the start of the arc itself, so the two explicit lines aren't needed.

removing the two lines

Additionally, if you change the initial point you're moving the path to to have the same x component as the center of the second arc, then the path will close itself. Here, all you need are the two arcs.

only the two arcs

That said, if the shape you're looking to create is a rounded rectangle like this, then you can use the UIBezierPath(roundedRect:cornerRadius:) convenience initializer:

let radius = min(layerView.bounds.width, layerView.bounds.height) / 2.0
let path = UIBezierPath(roundedRect: layerView.bounds, cornerRadius: radius)