Dominik Hadl Dominik Hadl - 16 days ago 7
Objective-C Question

Using circular progress bar with masked image?

I am working on a circular progress bar for custom game center achievements view and I have kind of "hit the wall". I am struggling with this for over two hours and still cannot get it to work.

The thing is, that I need a circular progress bar, where I would be able to set (at least) the track(fill) image. Because my progress fill is a rainbow like gradient and I am not able to achieve the same results using only code.

I was messing with CALayers, and drawRect method, however I wasn't successful. Could you please give me a little guidance?

Here are examples of the circular progress bars:
https://github.com/donnellyk/KDGoalBar
https://github.com/danielamitay/DACircularProgress

I just need the fill to be an masked image, depending on the progress. If you could even make it work that the progress would be animated, that would be really cool, but I don't require help with that :)

Thanks, Nick

omz omz
Answer

You basically just need to construct a path that defines the area to be filled (e.g. using CGPathAddArc), clip the graphics context to that path using CGContextClip and then just draw your image.

Here's an example of a drawRect: method you could use in a custom view:

- (void)drawRect:(CGRect)rect
{
    CGContextRef ctx = UIGraphicsGetCurrentContext();

    CGFloat progress = 0.7f; //This would be a property of your view
    CGFloat innerRadiusRatio = 0.5f; //Adjust as needed

    //Construct the path:
    CGMutablePathRef path = CGPathCreateMutable();
    CGFloat startAngle = -M_PI_2;
    CGFloat endAngle = -M_PI_2 + MIN(1.0f, progress) * M_PI * 2;
    CGFloat outerRadius = CGRectGetWidth(self.bounds) * 0.5f - 1.0f;
    CGFloat innerRadius = outerRadius * innerRadiusRatio;
    CGPoint center = CGPointMake(CGRectGetMidX(self.bounds), CGRectGetMidY(self.bounds));
    CGPathAddArc(path, NULL, center.x, center.y, innerRadius, startAngle, endAngle, false);
    CGPathAddArc(path, NULL, center.x, center.y, outerRadius, endAngle, startAngle, true);
    CGPathCloseSubpath(path);
    CGContextAddPath(ctx, path);
    CGPathRelease(path);

    //Draw the image, clipped to the path:
    CGContextSaveGState(ctx);
    CGContextClip(ctx);
    CGContextDrawImage(ctx, self.bounds, [[UIImage imageNamed:@"RadialProgressFill"] CGImage]);
    CGContextRestoreGState(ctx);
}

To keep it simple, I've hard-coded a few things – you obviously need to add a property for progress and call setNeedsDisplay in the setter. This also assumes that you have an image named RadialProgressFill in your project.

Here's an example of what that would roughly look like:

Screenshot

I hope you have a better-looking background image. ;)