Drux
Drux

Reputation: 12670

Animated pie chart that can animate in both directions

I am trying to build an animated pie chart that can animate the pie in both directions depending on whether the value of its progress increases or decreases. I've started with sample code provided here and made some adjustments as suggested here.

The trouble is that the back-animation is not yet quite working. It only partially clears the pie. I am not quite sure whether this is due to me using animations incorrectly or calculating coordinates incorrectly, for if I insert CGContextSetFillColorWithColor(context, UIColor.redColor.CGColor); into the first if branch and change blend mode to kCGBlendModeNormal also there the result is similarly off.

So here is my current code (excerpt from PieLayer.m) -- who can suggest improvements?

- (void)drawInContext:(CGContextRef)context {
    PieView *pieView = self.delegate;
    NSAssert(pieView != nil, nil);

    CGRect rect = CGRectInset(self.bounds, 1, 1);

    CGFloat radius = MIN(CGRectGetMidX(rect), CGRectGetMidY(rect));
    CGPoint center = CGPointMake(radius, CGRectGetMidY(rect));

    CGContextSetFillColorWithColor(context, UIColor.clearColor.CGColor);
    CGContextSetStrokeColorWithColor(context, pieView.circumferenceTintColor.CGColor);
    CGContextSetLineWidth(context, 2.0f);

    CGContextFillEllipseInRect(context, rect);
    CGContextStrokeEllipseInRect(context, rect);

    CGFloat startAngle   = -M_PI / 2;
    CGFloat endAngle     = self.progress     * 2 * M_PI + startAngle;
    CGFloat lastEndAngle = self.lastProgress * 2 * M_PI + startAngle;

    if (self.lastProgress > self.progress) {
        NSLog(@"clear sector between progress=%f and lastProgress=%f", self.progress, self.lastProgress);

        CGContextMoveToPoint(context, center.x, center.y);
        CGContextAddArc(context, center.x, center.y, radius, endAngle, lastEndAngle, 0);
        CGContextClosePath(context);

        CGContextSetBlendMode(context, kCGBlendModeClear);
        CGContextFillPath(context);
    }

    if (self.progress > 0) {
        NSLog(@"draw sector to progress=%f (lastProgress=%f)", self.progress, self.lastProgress);

        CGContextSetFillColorWithColor(context, pieView.sectorTintColor.CGColor);

        CGContextMoveToPoint(context, center.x, center.y);
        CGContextAddArc(context, center.x, center.y, radius, startAngle, endAngle, 0);
        CGContextClosePath(context);

        CGContextSetBlendMode(context, kCGBlendModeNormal);
        CGContextFillPath(context);
    }

    ((PieLayer *)self.modelLayer).lastProgress = self.progress;

    [super drawInContext:context];
}

Upvotes: 0

Views: 203

Answers (1)

Pietro Saccardi
Pietro Saccardi

Reputation: 2622

The code in the answer you posted (animated CAShapeLayer pie) works fine in both directions for me, even using clearColor: see this video.
The only situation in which clearColor does not produce a correct behavior is when the background color is solid and the foreground is clear: in that case a change in the blending mode is required, as you pointed out in your code.

I tried with these three configuration, and in all the cases the layer was responding correctly to -setProgress:.

  • DZRoundProgressView as subview of the main UIView;
  • Class of main view set to DZRoundProgressView;
  • DZRoundProgressView as sublayer of the main UIView;

Moreover, it is unlikely that the content of a layer in the previous frame is still visible at the next draw call: apparently, the default behavior of -drawInContext: is to clear the context before execution, as this question says.
If you are not working with the contents property of the layer, and you are not hacking the drawing pipe, then there could be something wrong with the value of self.progress; perhaps some issue with presentation layer and model layer, or in the implementation of the property.

Indeed this doesn't solve your problem, since I couldn't reproduce it or isolate it, but should allow you to work around it.

Upvotes: 1

Related Questions