iOS Animation: CADisplayLink vs CAShapeLayer

1.2k views Asked by At

I'm writing a timer, similar to the timer available in the horizontal version of the Clock app, except that it starts full and runs down to empty.

I implemented the timer, and it works great for larger times, but it's unable to animate fast enough for quick animations (I'm aiming for 400ms total animation time at its fastest) because I animate using the drawRect method. To illustrate, here's my timer code:

class SSTimerView: UIView {
    var timer: NSTimer?
    var startAngle: CGFloat = (CGFloat) (M_PI * 1.5)
    var endAngle: CGFloat = (CGFloat) (M_PI * 1.5) + (CGFloat) (M_PI * 2)
    var percent: CGFloat? = 100 

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

    override func drawRect(rect: CGRect) {
        var bezierPath = UIBezierPath()
        //Create our arc with the correct angles
        bezierPath.addArcWithCenter(CGPointMake(rect.size.width/2, rect.size.height/2),
            radius: 130,
            startAngle: startAngle,
            endAngle: (endAngle - startAngle) * (percent! / 100.0) + startAngle,
            clockwise: true
        )
        //Set the display for the path and stroke it.
        bezierPath.lineWidth = 20
        Slide.sharedInstance.currentInstruction!.colorFamily.lighterColor.setStroke()
        bezierPath.stroke()
    }
}

I've been reading up on CADisplayLink, and I think it may be a viable option, but I've also heard that CADisplayLink is not appropriate in all circumstances. Is this one of them? If CADisplayLink is not appropriate here, what CA class should I be using?

1

There are 1 answers

1
Rob On BEST ANSWER

As you know, when manually animating CADisplayLink is always better than NSTimer. (For a discussion why, see WWDC 2014 video Building Interruptible and Responsive Interactions, starting about 14 minutes into the video.) So, if you stick with your existing pattern, definitely shift to CADisplayLink.

But if you want to animate the drawing of this path, CABasicAnimation is far easier:

var shapeLayer: CAShapeLayer!

func addShapeLayer() {
    shapeLayer     = CAShapeLayer()
    let startAngle = CGFloat(M_PI * 1.5)
    let endAngle   = startAngle + CGFloat(M_PI * 2)

    let bezierPath = UIBezierPath()

    bezierPath.addArcWithCenter(CGPointMake(view.bounds.size.width/2, view.bounds.size.height/2),
        radius: 130.0,
        startAngle: startAngle,
        endAngle: endAngle,
        clockwise: true
    )

    shapeLayer.path        = bezierPath.CGPath
    shapeLayer.strokeColor = UIColor.blueColor().CGColor
    shapeLayer.fillColor   = UIColor.clearColor().CGColor
    shapeLayer.lineWidth   = 20.0

    view.layer.addSublayer(shapeLayer)
}

func animateShapeLayer() {
    let animation       = CABasicAnimation(keyPath: "strokeEnd")
    animation.duration  = 0.4
    animation.fromValue = 0.0
    animation.toValue   = 1.0
    shapeLayer.addAnimation(animation, forKey: "strokeEndAnimation")
}

The CADisplayLink approach is great if you need more customized behavior, but if you just want to animate the drawing of a path over a certain duration, CABasicAnimation is easiest (and is likely to offer the best performance).