Reputation: 146
I'm trying to animate drawing sections in my PieChart view. So, every sector has it's own colour, and sector grows from some start to end angles.
For that, I've used
-(id<CAAction>)actionForKey:(NSString *)event {
if ([event isEqualToString:@"startAngle"] ||
[event isEqualToString:@"endAngle"]) {
return [self makeAnimationForKey:event];
}
return [super actionForKey:event];
}
-(CABasicAnimation *)makeAnimationForKey:(NSString *)key {
CABasicAnimation *animation = [CABasicAnimation animationWithKeyPath:key];
// set up animation
return animation;
}
So, these methods are working pretty well, but the problem is in using CGContextDrawLinearGradient
instead of CGContextSetStrokeColorWithColor
for colour segment. With CGContextSetStrokeColorWithColor
segment drawing step by step, while I'm increasing end agle, but with CGContextDrawLinearGradient
it's missing all intermediate values and drawing only final segment size. So, for user there is no any kid of animation only drawing segment at once, there is no any dynamic.
-(void)drawInContext:(CGContextRef)ctx {
CGPoint center = CGPointMake(self.bounds.size.width / 2, self.bounds.size.height / 2);
CGFloat radius = MIN(center.x, center.y);
CGContextBeginPath(ctx);
CGContextMoveToPoint(ctx, center.x, center.y);
CGPoint p1 = CGPointMake(center.x + radius * cosf(self.startAngle), center.y + radius * sinf(self.startAngle));
CGPoint p2 = CGPointMake(center.x + radius * cosf(self.endAngle), center.y + radius * sinf(self.endAngle));
CGContextAddLineToPoint(ctx, p1.x, p1.y);
int clockwise = self.startAngle > self.endAngle;
CGContextAddArc(ctx, center.x, center.y, radius, self.startAngle, self.endAngle, clockwise);
CGContextSaveGState(ctx);
CGContextClip(ctx);
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(space, (CFArrayRef)self.gradients, NULL);
CGContextDrawLinearGradient(ctx, gradient, p1, p2, kCGGradientDrawsBeforeStartLocation);
// CGContextSetFillColorWithColor working properly
// CGContextSetFillColorWithColor(ctx, self.fillColor.CGColor);
CGContextSetStrokeColorWithColor(ctx, self.strokeColor.CGColor);
CGContextSetLineWidth(ctx, self.strokeWidth);
CGContextDrawPath(ctx, kCGPathFillStroke);
}
Any comments will be appreciated, because I'm newbie with CoreGraphics.
Thank you!
Upvotes: 1
Views: 1510
Reputation: 24476
I took a stab at this using a composited Core Animation Layer. It inherits from CAGradientLayer
and then uses a CAShapeLayer
to take advantage of the strokeStart
and strokeEnd
properties. The shape layer is used to mask off the gradient layer which allows only shape layer alpha to show through--which in this case is whatever the strokeStart
and strokeEnd
properties are set to. This allows you to dynamically set the pie sizes. Here is the visual output of what I came up with:
The reason I used a gradient layer as the base class is because you were showing a gradient in your drawInContext:
code. You could use a regular CALayer
if you preferred.
Here is how I define the inherited layer class's init method:
- (instancetype)initWithRect:(CGRect)rect
{
self = [super init];
if (self) {
// Give it some default gradient colors
self.colors = @[(id)[[UIColor darkGrayColor] CGColor],
(id)[[UIColor lightGrayColor] CGColor]];
// Linear gradient from left side to right side
self.startPoint = CGPointMake(0.0f, 0.5f);
self.endPoint = CGPointMake(1.0f, 0.5f);
// Set our bounds based on what was passed in
self.bounds = rect;
// Initialize our shape layer and set its bounds and position
_shapeLayer = [CAShapeLayer layer];
_shapeLayer.bounds = rect;
_shapeLayer.position = CGPointMake(self.bounds.size.width/2.0f,
self.bounds.size.height/2.0f);
// Create an inner rect we'll use for the bezier path rectangle
CGRect innerRect = CGRectInset(rect, self.bounds.size.width/4.0f,
self.bounds.size.height/4.0f);
UIBezierPath *bezierPath = [UIBezierPath bezierPathWithRoundedRect:innerRect
cornerRadius:self.bounds.size.width/2.0f];
// Set the path
_shapeLayer.path = [bezierPath CGPath];
// No fill color
_shapeLayer.fillColor = [[UIColor clearColor] CGColor];
// This color doesn't matter since we only care about whether or
// not there is alpha when masking.
_shapeLayer.strokeColor = [[UIColor redColor] CGColor];
// Set the line with to half of the size since the stroke
// is centered
_shapeLayer.lineWidth = self.bounds.size.width/2.0f;
// Set the mask
self.mask = _shapeLayer;
}
return self;
}
Now you can declare each of your pie pieces like this:
MLGradientPieLayer *pie = [[MLGradientPieLayer alloc] initWithRect:kRect];
pie.colors = @[(id)[[UIColor orangeColor] CGColor], (id)[[UIColor yellowColor] CGColor]];
pie.position = kPosition;
[pie setStart:0.0f];
[pie setEnd:0.15f];
[self.view.layer addSublayer:pie];
You can see a full project on Github here: https://github.com/perlmunger/GradientPieLayer.git
I didn't add animations, however, you can animate the strokeStart
and strokeEnd
properties of the inner CAShapeLayer
called (unimaginatively) _shapeLayer
.
Upvotes: 1
Reputation: 56635
If you need to do custom animations have a look at:
Together they teach you a lot of good ways to do custom animations.
Upvotes: 0