如何使用CAShapeLayer和UIBezierPath绘制一个平滑的圆? [英] How to draw a smooth circle with CAShapeLayer and UIBezierPath?

查看:126
本文介绍了如何使用CAShapeLayer和UIBezierPath绘制一个平滑的圆?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我试图通过使用CAShapeLayer并在其上设置圆形路径来绘制描边圆。但是,此方法在渲染到屏幕时始终不如使用borderRadius或直接在CGContextRef中绘制路径准确。

I am attempting to draw a stroked circle by using a CAShapeLayer and setting a circular path on it. However, this method is consistently less accurate when rendered to the screen than using borderRadius or drawing the path in a CGContextRef directly.

以下是这三种方法的结果:< img src = https://i.stack.imgur.com/iYdyI.png alt =在此处输入图片描述>

Here are the results of all three methods:

请注意,第三个效果不佳渲染,尤其是在顶部和底部的笔触内部。

Notice that the third is poorly rendered, especially inside the stroke on the top and bottom.

已经 contentsScale 属性设置为 [UIScreen mainScreen] .scale

这是我为这三个圆圈绘制的代码。要使CAShapeLayer平滑绘制,缺少什么?

Here is my drawing code for these three circles. What’s missing to make the CAShapeLayer draw smoothly?

@interface BCViewController ()

@end

@interface BCDrawingView : UIView

@end

@implementation BCDrawingView

- (id)initWithFrame:(CGRect)frame
{
    if ((self = [super initWithFrame:frame])) {
        self.backgroundColor = nil;
        self.opaque = YES;
    }

    return self;
}

- (void)drawRect:(CGRect)rect
{
    [super drawRect:rect];

    [[UIColor whiteColor] setFill];
    CGContextFillRect(UIGraphicsGetCurrentContext(), rect);

    CGContextSetFillColorWithColor(UIGraphicsGetCurrentContext(), NULL);
    [[UIColor redColor] setStroke];
    CGContextSetLineWidth(UIGraphicsGetCurrentContext(), 1);
    [[UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, 4, 4)] stroke];
}

@end

@interface BCShapeView : UIView

@end

@implementation BCShapeView

+ (Class)layerClass
{
    return [CAShapeLayer class];
}

- (id)initWithFrame:(CGRect)frame
{
    if ((self = [super initWithFrame:frame])) {
        self.backgroundColor = nil;
        CAShapeLayer *layer = (id)self.layer;
        layer.lineWidth = 1;
        layer.fillColor = NULL;
        layer.path = [UIBezierPath bezierPathWithOvalInRect:CGRectInset(self.bounds, 4, 4)].CGPath;
        layer.strokeColor = [UIColor redColor].CGColor;
        layer.contentsScale = [UIScreen mainScreen].scale;
        layer.shouldRasterize = NO;
    }

    return self;
}

@end


@implementation BCViewController

- (void)viewDidLoad
{
    [super viewDidLoad];

    UIView *borderView = [[UIView alloc] initWithFrame:CGRectMake(24, 104, 36, 36)];
    borderView.layer.borderColor = [UIColor redColor].CGColor;
    borderView.layer.borderWidth = 1;
    borderView.layer.cornerRadius = 18;
    [self.view addSubview:borderView];

    BCDrawingView *drawingView = [[BCDrawingView alloc] initWithFrame:CGRectMake(20, 40, 44, 44)];
    [self.view addSubview:drawingView];

    BCShapeView *shapeView = [[BCShapeView alloc] initWithFrame:CGRectMake(20, 160, 44, 44)];
    [self.view addSubview:shapeView];

    UILabel *borderLabel = [UILabel new];
    borderLabel.text = @"CALayer borderRadius";
    [borderLabel sizeToFit];
    borderLabel.center = CGPointMake(borderView.center.x + 26 + borderLabel.bounds.size.width/2.0, borderView.center.y);
    [self.view addSubview:borderLabel];

    UILabel *drawingLabel = [UILabel new];
    drawingLabel.text = @"drawRect: UIBezierPath";
    [drawingLabel sizeToFit];
    drawingLabel.center = CGPointMake(drawingView.center.x + 26 + drawingLabel.bounds.size.width/2.0, drawingView.center.y);
    [self.view addSubview:drawingLabel];

    UILabel *shapeLabel = [UILabel new];
    shapeLabel.text = @"CAShapeLayer UIBezierPath";
    [shapeLabel sizeToFit];
    shapeLabel.center = CGPointMake(shapeView.center.x + 26 + shapeLabel.bounds.size.width/2.0, shapeView.center.y);
    [self.view addSubview:shapeLabel];
}


@end

编辑:对于那些看不到差异的人,我在彼此上方绘制了一个圆圈并放大了:

For those who cannot see the difference, I've drawn circles on top of each other and zoomed in:

在这里,我绘制了一个带有<$ c $的红色圆圈c> drawRect:,然后用 drawRect:在其顶部再次绘制一个相同的圆圈。注意有限的红色出血。这两个圈子都是平稳的(与 cornerRadius 实现相同):

Here I've drawn a red circle with drawRect:, and then drawn an identical circle with drawRect: again in green on top of it. Note the limited bleed of red. Both of these circles are "smooth" (and identical to the cornerRadius implementation):

在第二个示例中,您将看到问题。我曾经使用红色的 CAShapeLayer 绘制过一次,然后再次使用同一路径的 drawRect:实现来绘制,但为绿色。请注意,从下方的红色圆圈可以看到更多的不一致性和更多的出血。显然,它是以不同(且更糟)的方式绘制的。

In this second example, you'll see the issue. I've drawn once using a CAShapeLayer in red, and again on top with a drawRect: implementation of the same path, but in green. Note that you can see a lot more inconsistency with more bleed from the red circle underneath. It's clearly being drawn in a different (and worse) fashion.

推荐答案

谁知道有这么多种绘制圆形的方法?

Who knew there are so many ways to draw a circle?


TL; DR:如果要使用 CAShapeLayer 仍然可以平滑的圆,您需要仔细使用 shouldRasterize rasterizationScale

TL;DR: If you want to use CAShapeLayer and still get smooth circles, you'll need to use shouldRasterize and rasterizationScale carefully.

原始

这是您原来的 CAShapeLayer 和与 drawRect 版本的差异。我用带Retina显示屏的iPad Mini截取了屏幕截图,然后在Photoshop中对其进行了按摩,然后将其放大到200%。如您所见, CAShapeLayer 版本具有明显的差异,尤其是在左右边缘(差异中最暗的像素)上。

Here's your original CAShapeLayer and a diff from the drawRect version. I made a screenshot off my iPad Mini with Retina Display, then massaged it in Photoshop, and blew it up to 200%. As you can clearly see, the CAShapeLayer version has visible differences, especially on the left and right edges (darkest pixels in the diff).

以屏幕比例光栅化

让我们在屏幕比例下进行光栅化,该比例应为 2.0 在视网膜设备上。添加此代码:

Let's rasterize at screen scale, which should be 2.0 on retina devices. Add this code:

layer.rasterizationScale = [UIScreen mainScreen].scale;
layer.shouldRasterize = YES;

请注意, rasterizationScale 默认为 1.0 甚至在视网膜设备上,这也解释了默认 shouldRasterize 的模糊性。

Note that rasterizationScale defaults to 1.0 even on retina devices, which accounts for the fuzziness of default shouldRasterize.

现在圆变得更平滑了,但是不良位(差异中最暗的像素)已移至顶部和底部边缘。

The circle is now a little smoother, but the bad bits (darkest pixels in the diff) have moved to the top and bottom edges. Not appreciably better than no rasterizing!

以2倍的屏幕比例光栅化

< img src = https://i.stack.imgur.com/DiWph.png alt =在此处输入图片描述>

layer.rasterizationScale = 2.0 * [UIScreen mainScreen].scale;
layer.shouldRasterize = YES;

这会以2倍屏幕比例或最高 4.0光栅化路径。 code>在视网膜设备上。

This rasterizes the path at 2x screen scale, or up to 4.0 on retina devices.

现在,圆圈明显更平滑了,差异也更轻了并且均匀分布。

The circle is now visibly smoother, the diffs are much lighter and spread out evenly.

我也在《 Instruments:Core Animation》中运行了此程序,并且在Core Animation Debug Options中没有看到任何主要差异。但是,它可能会变慢,因为它的缩放比例不只是将屏幕外的位图拖到屏幕上。制作动画时,您可能还需要临时设置 shouldRasterize = NO

I also ran this in Instruments: Core Animation and didn't see any major differences in the Core Animation Debug Options. However it may be slower since it's downscaling not just blitting an offscreen bitmap to the screen. You may also need to temporarily set shouldRasterize = NO while animating.

什么不起作用


  • 自行设置 shouldRasterize = YES 在视网膜设备上,这看起来很模糊,因为 rasterizationScale!= screenScale

设置 contentScale = screenScale 由于 CAShapeLayer 不会绘制到内容,无论是否栅格化,都不会影响再现。

Set contentScale = screenScale. Since CAShapeLayer doesn't draw into contents, whether or not it is rasterizing, this doesn't affect the rendition.

致谢 Humaan 的Jay Hollywood,他是一位敏锐的图形设计师,他首先向我指出了这一点。

Credit to Jay Hollywood of Humaan, a sharp graphic designer who first pointed it out to me.

这篇关于如何使用CAShapeLayer和UIBezierPath绘制一个平滑的圆?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆