13

I've been making a simple UIBezierPath animation with Swift. This path consists on creating a rounded rectangle with a colored border. The animation must be the drawing of the colored border. To do so, I've created a CAShapeLayer with a UIBezierPath(roundedRect:, cornerRadius: )

let layer = CAShapeLayer()
var viewPrueba = UIView()

override func viewDidLoad() {
    super.viewDidLoad()
    // Do any additional setup after loading the view, typically from a nib.

    viewPrueba = UIView(frame: CGRectMake(self.view.frame.width/2-100, self.view.frame.height/2 - 100, 200, 200))
    self.view.addSubview(viewPrueba)
    let path = UIBezierPath(roundedRect: CGRectMake(0, 0, 200, 200), cornerRadius: 40.0)
    layer.path = path.CGPath
    layer.fillColor = UIColor.clearColor().CGColor
    layer.strokeColor = UIColor.blueColor().CGColor
    layer.strokeStart = 0.0
    layer.strokeEnd = 0.0
    layer.lineWidth = 4.0
    layer.lineJoin = kCALineJoinRound
    viewPrueba.layer.addSublayer(layer)
    let tapGR = UITapGestureRecognizer(target: self, action: #selector(ViewController.anim))
    self.view.addGestureRecognizer(tapGR)
}

func anim() {
    let anim1 = CABasicAnimation(keyPath: "strokeEnd")
    anim1.fromValue         = 0.0
    anim1.toValue           = 1.0
    anim1.duration          = 4.0
    anim1.repeatCount       = 0
    anim1.autoreverses      = false
    anim1.removedOnCompletion = false
    anim1.additive = true
    anim1.fillMode = kCAFillModeForwards
    self.layer.addAnimation(anim1, forKey: "strokeEnd")
}`

It works well. The only problem is that the animation starts from the top-left part of the square and not from the top-center. How can I do that?

The only thing I've found in order to achieve this is by doing it with a circle and not a rectangle, which is not what we want.

This is where the animation starts

Thanks

Mahendra
  • 8,448
  • 3
  • 33
  • 56
rulilg
  • 1,604
  • 4
  • 15
  • 19

2 Answers2

17

CoreAnimate animated as the same order as which the UIBezierPath was drawn.
The system method

+ (instancetype)bezierPathWithRoundedRect:(CGRect)rect cornerRadius:(CGFloat)cornerRadius;     

return a UIBezierPath which was drawn from the top-left,so your animation started from the top-left.
But you can create your own UIBezierPath drawn form top-center:

func centerStartBezierPath(frame:CGRect,cornerRadius:CGFloat) -> UIBezierPath {
    let path = UIBezierPath()
    path.moveToPoint(CGPointMake(frame.width/2.0, 0))
    path.addLineToPoint(CGPointMake(frame.width-cornerRadius, 0))
    path.addArcWithCenter(CGPointMake(frame.width-cornerRadius, cornerRadius),
                          radius: cornerRadius,
                          startAngle: CGFloat(-M_PI/2),
                          endAngle: 0,
                          clockwise: true)
    path.addLineToPoint(CGPointMake(frame.width, frame.height-cornerRadius))
    path.addArcWithCenter(CGPointMake(frame.width-cornerRadius, frame.height-cornerRadius),
                          radius: cornerRadius,
                          startAngle: 0,
                          endAngle: CGFloat(M_PI/2),
                          clockwise: true)
    path.addLineToPoint(CGPointMake(cornerRadius, frame.height))
    path.addArcWithCenter(CGPointMake(cornerRadius, frame.height-cornerRadius),
                          radius: cornerRadius,
                          startAngle: CGFloat(M_PI/2),
                          endAngle: CGFloat(M_PI),
                          clockwise: true)
    path.addLineToPoint(CGPointMake(0, cornerRadius))
    path.addArcWithCenter(CGPointMake(cornerRadius, cornerRadius),
                          radius: cornerRadius,
                          startAngle: CGFloat(M_PI),
                          endAngle: CGFloat(M_PI*3/2),
                          clockwise: true)
    path.closePath()

    path.applyTransform(CGAffineTransformMakeTranslation(frame.origin.x, frame.origin.y))

    return path;
}    

And it works like this: top-center start animation
You can also change the code,and start from any point you want.

wj2061
  • 6,778
  • 3
  • 36
  • 62
  • Works like charm! Thank you! :) – rulilg May 01 '16 at 16:06
  • Nicely done. (voted) It would help if you explained how it works, and even included a diagram with the different line segments, arcs of the circle, etc., and annotated each drawing step. (I usually draw out the outer rectangle and then an inner rectangle inset by the corner radius, and draw the full corner circles in low opacity, with the curved corners at full opacity. – Duncan C Feb 26 '21 at 01:33
3

Swift syntax changed much during these three years. Here is my updated version of originally accepted answer, but in Swift 5.1+

private extension UIBezierPath {

    convenience init(roundedRectFromCenter frame: CGRect, cornerRadius: CGFloat) {
        self.init()

        move(to: CGPoint(x: frame.width / 2, y: 0))
        addLine(to: CGPoint(x: frame.width - cornerRadius, y: 0))
        addArc(
            withCenter: CGPoint(x: frame.width - cornerRadius, y: cornerRadius),
            radius: cornerRadius,
            startAngle: -.pi / 2,
            endAngle: 0,
            clockwise: true
        )
        addLine(to: CGPoint(x: frame.width, y: frame.height - cornerRadius))
        addArc(
            withCenter: CGPoint(x: frame.width - cornerRadius, y: frame.height - cornerRadius),
            radius: cornerRadius,
            startAngle: 0,
            endAngle: .pi / 2,
            clockwise: true
        )
        addLine(to: CGPoint(x: cornerRadius, y: frame.height))
        addArc(
            withCenter: CGPoint(x: cornerRadius, y: frame.height - cornerRadius),
            radius: cornerRadius,
            startAngle: .pi / 2,
            endAngle: .pi,
            clockwise: true
        )
        addLine(to: CGPoint(x: 0, y: cornerRadius))
        addArc(
            withCenter: CGPoint(x: cornerRadius, y: cornerRadius),
            radius: cornerRadius,
            startAngle: .pi,
            endAngle: .pi * 3 / 2,
            clockwise: true
        )

        close()
        apply(CGAffineTransform(
            translationX: frame.origin.x,
            y: frame.origin.y
        ))
    }

}
Timur Bernikovich
  • 5,660
  • 4
  • 45
  • 58