简体   繁体   中英

Effect of UIView coordinate system on circular version of UIProgressView

I need a class derived from UIView that indicates progress by filling an increasingly large sector of a circle (as progress goes from 0 to 1 its angle should grow by 2 PI). It's like a circular version of UIProgressView .

The following code does not yet quite do the job: it produces increasingly many small sectors stacked in a flickering zebra pattern. Presumably the code is messing up its coordinates with UIView flipping its coordinate system, with CGContextAddArc ´s (apparently) counter-intuitive definition of "clockwise", etc.

What's the reason for the misbehavior and how can it be fixed?

#define PERCENTAGE_TO_RADIANS(PERCENTAGE) ((PERCENTAGE) * 2 * M_PI - M_PI_2)

- (void)drawRect:(CGRect)rect
{
    NSAssert(!self.opaque,                     nil);
    NSAssert(!self.clearsContextBeforeDrawing, nil);

    CGSize  size   = self.bounds.size;
    CGPoint center = [self.superview convertPoint:self.center toView:self];

    NSAssert(size.width == size.height, nil);
    NSAssert((self.progress >= 0) && (self.progress <= 1), nil);

    NSAssert(PERCENTAGE_TO_RADIANS(0   ) == -M_PI_2   , nil); //   0% progress corresponds to north
    NSAssert(PERCENTAGE_TO_RADIANS(0.25) == 0         , nil); //  25% progress corresponds to east
    NSAssert(PERCENTAGE_TO_RADIANS(0.5 ) == M_PI_2    , nil); //  50% progress corresponds to south
    NSAssert(PERCENTAGE_TO_RADIANS(0.75) == M_PI      , nil); //  75% progress corresponds to west
    NSAssert(PERCENTAGE_TO_RADIANS(1   ) == 3 * M_PI_2, nil); // 100% progress corresponds to north

    CGFloat x          = center.x;
    CGFloat y          = center.y;
    CGFloat radius     = size.width / 2.0;
    CGFloat startAngle = self.lastAngle;
    CGFloat endAngle   = PERCENTAGE_TO_RADIANS(self.progress);
    int     clockwise  = 0;

    if (self.progress > 0) {
        CGContextRef context = UIGraphicsGetCurrentContext();
        CGContextSaveGState(context);

        CGContextSetFillColorWithColor(context, self.sectorTintColor.CGColor);
        CGContextSetLineWidth(context, 1);

        NSAssert(startAngle <= endAngle, nil);
        CGContextMoveToPoint(context, x, y);
        CGContextAddArc(context, x, y, radius, startAngle, endAngle, clockwise);
        CGContextClosePath(context);

        CGContextFillPath(context);

        CGContextRestoreGState(context);
    }

    self.lastAngle = endAngle;
}

The most important thing you need to know is that positive Y is down, which makes the angles and the clockwise direction non-intuitive. You have two choices, either just go with it, or flip the Y-axis before drawing anything. In the sample code below, I took the latter approach.

The drawRect method is not accumulative. In other words, you are required to draw the entire sector on each call to drawRect . Hence there is no reason to keep track of the start angle, you should always start at angle PI/2 and draw the whole sector.

There is no need to save and restore the graphics state. The graphics context is created new each time drawRect is called.

The UIColor class has set , setFill , and setStroke methods that allow you to change fill and stroke colors without using CGColors.

With all that in mind, here's what the code looks like, assuming that self.progress is a number between 0.0 and 1.0

- (void)drawRect:(CGRect)rect
{
    if ( self.progress <= 0 || self.progress > 1.0 )
        return;

    CGFloat x = self.bounds.size.width  / 2.0;
    CGFloat y = self.bounds.size.height / 2.0;
    CGFloat r = (x <= y) ? x : y;

    CGFloat angle = M_PI_2 - self.progress * 2.0 * M_PI;

    CGContextRef context = UIGraphicsGetCurrentContext();

    // fix the y axis, so that positive y is up
    CGContextScaleCTM( context, 1.0, -1.0 );
    CGContextTranslateCTM( context, 0, -self.bounds.size.height );

    // draw the pie shape
    [self.sectorTintColor setFill];
    CGContextMoveToPoint( context, x, y );
    CGContextAddArc( context, x, y, r, M_PI_2, angle, YES );
    CGContextClosePath( context );

    CGContextFillPath( context );
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM