I'm working on a circular progress bar that I want to animate. Everything draws as expected and works but when I attempt to animate I get an immediate change instead of the animation. Any help would be appreciated.
Here is my layer:
@interface CircularProgressLayer : CALayer
@property (nonatomic, assign) CGFloat progressDegrees;
@property (nonatomic, assign) CGFloat trackWidth;
@end
@implementation CircularProgressLayer : CALayer
@dynamic progressDegrees;
@dynamic trackWidth;
- (id)initWithLayer:(id)layer
{
if ((self = [super initWithLayer:layer]))
{
if ([layer isKindOfClass:[CircularProgressLayer class]])
{
self.progressDegrees = ((CircularProgressLayer*)layer).progressDegrees;
}
}
return self;
}
// Instruct to Core Animation that a change in the custom property should automatically trigger a redraw of the layer.
+ (BOOL)needsDisplayForKey:(NSString*)key
{
if ([key isEqualToString:@"progressDegrees"])
{
return YES;
}
if ([key isEqualToString:@"trackWidth"])
{
return YES;
}
return [super needsDisplayForKey:key];
}
// Needed to support implicit animation of this property.
// Return the basic animation the implicit animation will leverage.
- (id<CAAction>)actionForKey:(NSString *)key
{
if ([key isEqualToString:@"progressDegrees"])
{
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
animation.fromValue = [[self presentationLayer] valueForKey:key];
animation.duration = 5.0;
animation.fillMode = kCAFillModeForwards;
animation.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseOut];
return animation;
}
return [super actionForKey:key];
}
As I understand it, returning the animation in the actionForKey:
should be all that is needed to make the animation work, but I get nothing. All of my drawing is currently contained in the drawLayer:inContext:
method of my view that implements this layer (cause it originated as drawRect code before I learned I had to put it in a layer to get it to animate, and I haven't converted everything over), but the sample code I downloaded from Apple makes it look like this is just fine.
Here is the drawing code:
-(void)drawLayer:(CALayer *)layer inContext:(CGContextRef)context
{
UIGraphicsPushContext(context);
[self drawTrackInContext:context];
[self drawProgressInContext:context];
UIGraphicsPopContext();
}
- (void)drawProgressInContext:(CGContextRef)context
{
if (self.progress < 1)
{
// No progress, get out
return;
}
CGSize size = self.bounds.size;
FlipContextVertically(size);
CGContextSaveGState(context);
{
UIBezierPath *circle = [self circle];
CGContextSetLineWidth(context, 0.0);
CGFloat degrees = 360.0;
for (int i = 0; i < degrees; i++)
{
if (i > ((RMONCircularProgressLayer *)self.layer).progressDegrees)
{
break;
}
CGFloat theta = 2 * M_PI * (CGFloat) i / degrees;
CGFloat x = self.radius * sin(theta);
CGFloat y = self.radius * cos(theta);
MovePathCenterToPoint(circle, CGPointMake(x, y));
OffsetPath(circle, CGSizeMake(size.width / 2, size.height / 2));
CGContextSetFillColorWithColor(context, [self colorForDegree:i].CGColor);
CGContextAddPath(context, circle.CGPath);
CGContextDrawPath(context, kCGPathFillStroke);
}
}
CGContextRestoreGState(context);
}
- (void)drawTrackInContext:(CGContextRef)context
{
CGContextSaveGState(context);
{
// Draw the circle covering middle of the screen
CGSize size = self.bounds.size;
CGContextSetLineWidth(context, self.trackWidth); // will only be filled color
CGContextSetStrokeColorWithColor(context, self.trackColor.CGColor);
CGContextSetFillColorWithColor(context, self.fillColor.CGColor);
CGContextSetAlpha(context, 1.0);
CGContextAddArc(context, (CGFloat)size.width/2.0, (CGFloat)size.height/2.0, self.radius, 0, (CGFloat)M_PI*2.0, 1);
CGContextDrawPath(context, kCGPathFillStroke);
}
CGContextRestoreGState(context);
}
drawTrackInContext
just draws a circle, where the progress circle will be contained. drawProgressInContext
is the drawing code I'm expecting to animate. There are some helper functions from iOS Drawing by Erica Sadun that do some context and path manipulation that I didn't include, the function names should be fairly self explanatory.
Any help would be greatly appreciated.
FYI: I'm more than willing to make this an explicit animation if that's easier, but I ended up going in this direction because I couldn't make that work either.
drawLayer:inContext:
– Eric Qian