UIView坐标系对圆形UIProgressView的影响

Posted

技术标签:

【中文标题】UIView坐标系对圆形UIProgressView的影响【英文标题】:Effect of UIView coordinate system on circular version of UIProgressView 【发布时间】:2014-06-04 20:42:54 【问题描述】:

我需要一个派生自UIView 的类,它通过填充越来越大的圆扇区来表示进度(随着进度从0 变为1,它的角度应该增加2 PI)。这就像UIProgressView 的循环版本。

下面的代码还不能很好地完成这项工作:它会生成越来越多的小扇区,以闪烁的斑马图案堆叠。据推测,代码将其坐标与 UIView flipping 其坐标系、CGContextAddArc 的(显然)“顺时针”的反直觉定义等混淆了。

不当行为的原因是什么?如何解决?

#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;

【问题讨论】:

【参考方案1】:

您需要知道的最重要的事情是正 Y 向下,这使得角度和顺时针方向不直观。您有两个选择,要么直接使用它,要么在绘制任何东西之前翻转 Y 轴。在下面的示例代码中,我采用了后一种方法。

drawRect 方法不是累积的。换句话说,您需要在每次调用drawRect 时绘制整个扇区。因此没有理由跟踪起始角度,您应该始终从角度 PI/2 开始并绘制整个扇区。

无需保存和恢复图形状态。每次调用 drawRect 时都会创建新的图形上下文。

UIColor 类具有 setsetFillsetStroke 方法,允许您在不使用 CGColors 的情况下更改填充和描边颜色。

考虑到所有这些,假设self.progress 是介于 0.0 和 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 );

【讨论】:

非常好,谢谢。你能再澄清一点吗?如果视图的clearsContextBeforeDrawing 设置为NO,我认为drawRect: 是累积的。但是您的建议是,如果是这样,则不会清除背景视图的上下文,但是无论如何都会在调用drawRect: 之前清除视图自己的视图(在rect 内),对吗? This 对clearsContextBeforeDrawing 很有帮助:“您无法通过执行以下操作来防止内容被擦除:[self setClearsContextBeforeDrawing: NO]; 这只是对图形引擎的提示,没有意义让它为您预先清除视图,因为无论如何您都可能需要重新绘制整个区域。它可能会阻止您的视图被自动擦除,但您不能依赖它。” 啊,是的,这很好地解释了它。现在我们只需要preservesContextFromPreviousDrawing 属性:)

以上是关于UIView坐标系对圆形UIProgressView的影响的主要内容,如果未能解决你的问题,请参考以下文章

如何将 UIView 裁剪为半圆形?

UILabel 在圆形自定义 UIView 中不可见

如何使用 Swift 在 UIView 中将圆形进度条居中?

为啥在任何 UIimageView 和 Uiview 上都无法访问圆形滑块

可调整大小的居中圆形 UIView

UIView 上的圆形图层的 Swift 掩码