Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

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

like image 225
matthias_buehlmann Avatar asked Sep 09 '14 13:09

matthias_buehlmann


2 Answers

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 
like image 130
Ash Avatar answered Dec 30 '22 20:12

Ash


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

like image 33
txulu Avatar answered Dec 30 '22 21:12

txulu