Search code examples
iosswiftanimationgeometrydrawrect

animate drawRect circle in Swift


For the initial question have a look at the edit-log of this post.

I mostly used the code from here and am now not sure what to put in outerCircleView(frame: ???):, so that it get's displayed correctly within the constrains I set for outerCirvleView: UIButton.

var circleLayer: CAShapeLayer!

@IBDesignable // enables rendering in StoryBoard
class outerCircleView: UIButton {

override init(frame: CGRect) {
    super.init(frame: frame)

    // Use UIBezierPath as an easy way to create the CGPath for the layer.
    // The path should be the entire circle.
    let circlePath = UIBezierPath(arcCenter: CGPoint(x: frame.size.width / 2.0, y: frame.size.height / 2.0), radius: (frame.size.width - 10)/2, startAngle: 0.0, endAngle: CGFloat(M_PI * 2.0), clockwise: true)

    // Setup the CAShapeLayer with the path, colors, and line width
    circleLayer = CAShapeLayer()
    circleLayer.path = circlePath.CGPath
    circleLayer.fillColor = UIColor.clearColor().CGColor
    circleLayer.strokeColor = UIColor.redColor().CGColor
    circleLayer.lineWidth = 5.0;

    // Don't draw the circle initially
    circleLayer.strokeEnd = 0.0

    // Add the circleLayer to the view's layer's sublayers
    layer.addSublayer(circleLayer)
}

required init(coder aDecoder: NSCoder!)
{
    super.init(coder: aDecoder)
}

func animateCircle(duration: NSTimeInterval) {
    // We want to animate the strokeEnd property of the circleLayer
    let animation = CABasicAnimation(keyPath: "strokeEnd")

    // Set the animation duration appropriately
    animation.duration = duration

    // Animate from 0 (no circle) to 1 (full circle)
    animation.fromValue = 0
    animation.toValue = 1

    // Do a linear animation (i.e. the speed of the animation stays the same)
    animation.timingFunction = CAMediaTimingFunction(name: kCAMediaTimingFunctionLinear)

    // Set the circleLayer's strokeEnd property to 1.0 now so that it's the
    // right value when the animation ends.
    circleLayer.strokeEnd = 1.0

    // Do the actual animation
    circleLayer.addAnimation(animation, forKey: "animateCircle")
    }
}

func addCircleView() {

    // Create a new CircleView
    var circleView = outerCircleView(frame: ???)

    view.addSubview(circleView)

    // Animate the drawing of the circle over the course of 1 second
    circleView.animateCircle(1.0)
}

Solution

  • The post you link spells out what to in detail.

    As explained in that post, you need to not use drawRect. (So the way to adapt your code is to start over with a new approach.) Instead you need to create a CAShapeLayer and install it as a sublayer of your view. You can then use a CABasicAnimation to animate the strokeEnd property of the shape layer.

    EDIT:

    Nowhere in your edited code do you set the frame of your shape layer.

    Add the following code to your layoutSubviews method: (create one if you don't have one already.)

    override func layoutSubviews()
    {
      var frame = self.layer.bounds
      circleLayer.frame = frame
    }
    

    Leave the code that creates your circlePath in init, since you only want to do that once.

    Move the code that creates your circle path into layoutSubviews. Your view's size often changes after init. layoutSubviews gets called if and when your view's size changes, which is what you want. (When the user rotates their phone your view might change sizes. If that happens layoutSubviews will be called again, and you will generate a new, correctly sized circle path, which is what you want to do. You want to resize your circle layer and rebuild your circle path each time your view changes size.)