I've created a animatable Core Graphics drawing using PaintCode. It's basically a circle meter (not unlike the Apple Watch rings), which basically fills up as a timer counts down. The meterLevel controls the fill level of the circle meter, from 0 to 100. Basically, if a timer is set to 10 seconds, I set the meterLevel every 1 seconds to 90, 80, 70, etc...
This works good, however the animation is only drawn ever 1 second, and looks quite choppy. Instead, I'd like it be a smooth continuous filling meter.
Looking around it seemed like subclassing CALayer and creating an implicit animation for the meterLevel property might be the way to go. So here is what I have:
import UIKit
class MeterControlView: UIView
{
var meterLevel: Int = 0 {
didSet {
self.layer.setValue(meterLevel, forKey: "meterLevel")
}
}
var meterText: String = "00:00:00" {
didSet {
self.layer.setValue(meterText, forKey: "meterText")
}
}
override class func layerClass() -> AnyClass {
return MeterControlLayer.self
}
override func drawRect(rect: CGRect) {
// Do nothing
}
}
class MeterControlLayer: CALayer
{
@NSManaged
var meterLevel: Int
var meterText: String = "00:00:00"
override class func needsDisplayForKey(key: String) -> Bool {
if (key == "meterLevel") {
return true
}
return super.needsDisplayForKey(key)
}
override func actionForKey(key: String) -> CAAction? {
if (key == "meterLevel") {
let anim: CABasicAnimation = CABasicAnimation.init(keyPath: key)
anim.fromValue = self.presentationLayer()?.meterLevel
anim.duration = 1.0
return anim
} else {
return super.actionForKey(key)
}
}
override func drawInContext(ctx: CGContext) {
super.drawInContext(ctx)
UIGraphicsPushContext(ctx)
XntervalStyleKit.drawMeterControl(frame: self.bounds, meterTime: meterText, meterLevelValue: CGFloat(meterLevel))
UIGraphicsPopContext()
}
}
This unfortunately, doesn't work exactly the way I would expect. The animation is still a bit choppy, though closer to what I want.
My question is more general though, is this the right way to go about accomplishing what I want to do? I couldn't figure out the right way to set the layer properties meterLevel and meterText, without using setValueForKey:. Is that the right way to do this?
Animation/Graphics is definitely new to me. I'm an embedded C software guy trying to get into iOS development as a hobby.
The animation is still a bit choppy, though closer to what I want.
Given this, it seems as if Core Animation is actually drawing your layer every frame (or trying to, anyway).
Unfortunately, once you perform custom layer drawing each frame, your performance becomes main thread-bound: that is, normally, for properties that Core Animation can natively animate (such as bounds), the animation itself is rendered in the render server, which operates out-of-process from your application and has its own, high-priority render thread. The main thread of your application is free to do whatever it wants during these types of animation without any interruption to the animation.
drawInContext(_:)
, however, is called on the main thread of your application. If you put a log statement or breakpoint there, is it called many times over the course of your animation duration? If so, then the layer is properly animating. Your drawing operations within this function may be what's holding up the animation.
Try setting drawsAsynchronously
to true
on your layer. This defers drawing commands to a background thread, and it can improve performance sometimes. (Note that most, if not all, UIGraphics and Core Graphics functions are thread safe as of iOS 4, so background drawing is safe.)
Additionally, depending on how complex your animation is, you may want to draw several intermediate representations in advance (in the background and in parallel, if possible). Store these somewhere in memory if they aren't too large so you can simply display some of these bitmaps in your layer instead of drawing them just-in-time.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With