iOS drawing in CALayers drawInContext is either pixelated or blurry on retina
Asked Answered
C

3

22

I have a custom UIView that draws something in an overwritten

- (void)drawRect:(CGRect)rect

this works fine and gives sharp results on retina screens.

However, now I would like to make the properties on which the drawing is based animatable. Creating animatable properties seems to be possible only on the CALayer, so instead of doing the drawing in UIView, I create a custom CALayer subclass and do the drawing inside

- (void) drawInContext:(CGContextRef)ctx

I use pretty much the exact same drawing code in this function as I used in the drawRect function of the custom UIView.

The result looks the same - however, it's not retina resolution but pixelated (large square pixels)

if I put

self.contentsScale = [UIScreen mainScreen].scale;

To the beginning of my drawInContext implementation, then instead of a pixelated result, I get a blurry result (as if the rendering is still performed in non-retina resolution and then upscaled to retina resolution).

what's the correct way to render sharp retina paths in CALayers drawInContext ?

here are some screenshots (the blue line is part of the custom drawing in question. the yellow part is just an image)

Drawn inside custom UIView's drawRect:

enter image description here

Drawn inside custom CALayer's drawInContext:

enter image description here

Drawin inside custom CALayer's drawInContext, with setting self.contentScale first:

enter image description here

For completeness, here's (a stripped down version of the) drawing code:

//if drawing inside custom UIView sublcass:
- (void)drawRect:(CGRect)rect
{
    CGContextRef currenctContext = UIGraphicsGetCurrentContext();
    [[UIColor blackColor] set];

    CGContextSetLineWidth(currenctContext, _lineWidth);
    CGContextSetLineJoin(currenctContext,kCGLineJoinRound);

    CGContextMoveToPoint(currenctContext,x1, y1);
    CGContextAddLineToPoint(currenctContext,x2, y2);
    CGContextStrokePath(currenctContext);
}

//if drawing inside custom CALayer subclass:
- (void) drawInContext:(CGContextRef)ctx {
{
    //self.contentsScale = [UIScreen mainScreen].scale;

    CGContextRef currenctContext = ctx;
    CGContextSetStrokeColorWithColor(currenctContext, [UIColor blackColor].CGColor);

    CGContextSetLineWidth(currenctContext, _lineWidth);
    CGContextSetLineJoin(currenctContext,kCGLineJoinRound);

    CGContextMoveToPoint(currenctContext,x1, y1);
    CGContextAddLineToPoint(currenctContext,x2, y2);
    CGContextStrokePath(currenctContext);
}

To restate what I want to achieve: I want to achieve the same crisp retina rendering as in the UIView approach, but when rendering in CALayer

Crocidolite answered 9/9, 2014 at 13:49 Comment(6)
in retina display, you need to multiply the height and width by 2.Pes
no, the size and location on screen is correctCrocidolite
I mean the screen you just create should be multiplied by 2, because retina display needs double resolution.Pes
Thank you for trying to help, but I think you did not properly understand my problem hereCrocidolite
What if you move self.contentsScale = [UIScreen mainScreen].scale to layer initialization method?Hilel
Did you ever solve this? I have the exact same issue, and using shouldRasterize and setting rasterizationScale have no effect.Torrent
S
19

The issue is most likely to be contentScale here; be aware that if you're assigning this to a custom view by overriding its layerClass function, the layer's content scale may be reset. There may be some other instances in which this also happens. To be safe, set the content scale only after the layer has been added to a view.

Try assigning the main screen's scale to your custom layer during your custom view's init method. In Swift 3 that looks like this:

layer.contentsScale = UIScreen.mainScreen().scale

Or, in Swift 4:

layer.contentsScale = UIScreen.main.scale
Semimonthly answered 21/8, 2016 at 20:48 Comment(2)
Perfect this fixed the pixelated problemsDeclinature
That solved the problem, thank you! Swift 5 2021Chefoo
R
9

Are you using shouldRasterize = YES in the layer? Try drawing in the CALayer subclass but set the rasterizationScale to the screen's scale.

Rahmann answered 6/2, 2015 at 13:43 Comment(2)
@AndresCanella on which device did you use 4.0 for rasterizationScale?Ruminate
@Ruminate I ended up using screen scale * 2.0 on i6. I was loosing crispness on 4.0.Southwestwardly
G
5

After adding layer to its superlayer. set shouldRasterize to YES , set contentsScale and resterizatioinScale to screen scale:

[self.progressView.layer addSublayer:self.progressLayer];
self.progressLayer.shouldRasterize = YES;
self.progressLayer.contentsScale = kScreenScale;
self.progressLayer.rasterizationScale = kScreenScale;

CABasicAnimation *animate = [CABasicAnimation animationWithKeyPath:@"progress"];// progress is a customized property of progressLayer
animate.duration = 1.5;
animate.beginTime = 0;
animate.fromValue = @0;
animate.toValue = @1;
animate.fillMode = kCAFillModeForwards;
animate.removedOnCompletion = NO;
animate.repeatCount = HUGE_VALF;
[self.progressLayer addAnimation:animate forKey:@"progressAnimation"];
Graze answered 13/10, 2016 at 6:46 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.