我试图通过使用CAShapeLayer并在其上设置圆形路径来绘制一个有冲程的圆.但是,与使用borderRadius或直接在CGContextRef中绘制路径相比,这种方法在渲染到屏幕上时的精度始终较低.

以下是所有三种方法的结果:在此输入图像描述

请注意,第三个渲染效果很差,尤其是在顶部和底部的笔划内部.

我将contentsScale属性设置为[UIScreen mainScreen].scale.

这是我画这三个圆的代码.要让CAShapeLayer顺利提款,还缺少什么?

@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

编辑:对于那些看不到差异的人,我已经在彼此上方画了圆圈,并放大了:

这里我画了一个drawRect:的红色圆圈,然后在上面画了一个同样的drawRect:绿色的圆圈.注意红色的有限出血.这两个圆圈都是"平滑的"(与cornerRadius实现相同):

在此处输入图像描述

在第二个示例中,您将看到问题.我用红色的CAShapeLayer画了一次,在上面画了一次同样路径的drawRect:实现,但是是绿色的.请注意,您可以从下面的红色圆圈中看到更多的不一致和更多的出血.它显然是以一种不同的(也是更糟糕的)方式绘制的.

在此处输入图像描述

推荐答案

谁知道画圆圈的方法有这么多呢?

TL;DR:如果你想使用CAShapeLayer并且仍然得到平滑的圆,你需要小心地使用shouldRasterizerasterizationScale.

Original

在此处输入图像描述

这是你原来的CAShapeLayerdrawRect版本的区别.我用带有视网膜显示屏的iPad Mini做了一个屏幕截图,然后在Photoshop中对其进行了按摩,并将其放大到200%.正如您可以清楚地看到的,CAShapeLayer版本有明显的差异,尤其是在左边缘和右边缘(差异中最暗的像素).

Rasterize at screen scale

在此处输入图像描述

让我们在屏幕上光栅化,在视网膜设备上应该是2.0.添加以下代码:

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

请注意,即使在视网膜设备上,rasterizationScale也默认为1.0,这解释了默认值shouldRasterize的模糊性.

圆现在稍微平滑了一点,但坏位(diff中最暗的像素)已经移到了上边缘和下边缘.不比没有光栅化好多少!

Rasterize at 2x screen scale

在此处输入图像描述

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

这会以2倍的屏幕比例栅格化路径,或者在视网膜设备上栅格化高达4.0倍的路径.

圆圈现在看起来更平滑了,差异也更轻了,分布也更均匀了.

我也在Instruments:Core Animation中运行了这个,在Core Animation Debug选项中没有看到任何重大区别.然而,它可能会更慢,因为它是在缩小比例,而不仅仅是将屏幕外的位图传送到屏幕上.您可能还需要在设置动画时临时设置shouldRasterize = NO.

What doesn't work

  • Set 100 by itself.在视网膜设备上,这看起来很模糊,因为rasterizationScale != screenScale.

  • Set 100.由于CAShapeLayer不绘制到contents中,无论是否光栅化,这都不会影响格式副本.

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

Ios相关问答推荐

不使用iOS 17修改器修改SWIFT用户界面视图

如何在将EXPO更新到50.0后修复react 本机错误

Swift导航远离视频导致崩溃

为什么EKEventStore().questFullAccessToEvents()可以与模拟器一起使用,而不能与真实设备一起使用?

如何在用户点击的位置使用SceneKit渲染球体?

应用程序被杀死时如何在 flutter ios 应用程序中播放音频?

在 SwiftUI 中重构提取到子视图的 GeometryReader 代码

TextField 重复输入 SwiftUI

快速禁用 UITextfield 的用户输入

'+entityForName: nil 不是合法的 NSManagedObjectContext 参数 - 核心数据

在 Grand Central Dispatch 中使用 dispatch_sync

界面生成器中 UIView 的边框 colored颜色 不起作用?

从 NSData 或 UIImage 中查找图像类型

为信用卡输入格式化 UITextField,例如 (xxxx xxxx xxxx xxxx)

如何在 Objective-C 中组合两个数组?

如何计算 Swift 数组中元素的出现次数?

Swift:如何在设备旋转后刷新 UICollectionView 布局

用 NavigationViewController 快速呈现 ViewController

在 iOS 7 中更改返回按钮会禁用滑动导航返回

NSURL 到带有 XCTest 的测试包中的文件路径