Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When do I need to set the contentsScale property of a CALayer?

In the documentation Apple states that you need to consider the scale factor of a layer when it:

  • Creates additional Core Animation layers with different scale factors and composites them into its own content
  • Sets the contents property of a Core Animation layer directly

The last statement is not totally clear to me. They also say:

Applications that use Core Animation layers directly to provide content may need to adjust their drawing code to account for scale factors. Normally, when you draw in your view’s drawRect: method, or in the drawLayer:inContext: method of the layer’s delegate, the system automatically adjusts the graphics context to account for scale factors.

Normallly or always? and this is valid only for view's hosted backing layer or every layer I add as a sublayer to the backing layer? because they also say:

If your application creates layers without an associated view, each new layer object’s scale factor is set to 1.0 initially. If you do not change that scale factor, and if you subsequently draw the layer on a high-resolution screen, the layer’s contents are scaled automatically to compensate for the difference in scale factors. If you do not want the contents to be scaled, you can change the layer’s scale factor to 2.0, but if you do so without providing high-resolution content, your existing content may appear smaller than you were expecting. To fix that problem, you need to provide higher-resolution content for your layer.


I'm having difficulties imagining a layer without a view, I can imagine a hierarchy of layers but as soon as I want to display them I should add it as a subhierachy of the view's backing layer, or do exist some other means to use them (like creating and just renderring in a context)?

Core Animation’s compositing engine looks at the contentsScale property of each layer to determine whether the contents of that layer need to be scaled during compositing. If your application creates layers without an associated view, each new layer object’s scale factor is set to 1.0 initially. If you do not change that scale factor, and if you subsequently draw the layer on a high-resolution screen, the layer’s contents are scaled automatically to compensate for the difference in scale factors. If you do not want the contents to be scaled, you can change the layer’s scale factor to 2.0, but if you do so without providing high-resolution content, your existing content may appear smaller than you were expecting. To fix that problem, you need to provide higher-resolution content for your layer.

What does that actually mean? When I do something like that layer.contents = (id)image.CGImage I need to set the contentsScale.
Do I also need to set it when my layer draws it's contents calling the delegate method drawLayer:inContext: and the layer isn't the backing layer of a view?

like image 861
Andrea Avatar asked Aug 27 '13 07:08

Andrea


People also ask

What is the layer property on a UIView object?

All UIView subclasses have a layer property, which is responsible for drawing their contents efficiently. These layers are powered by Core Animation, which handles all the drawing and animation that UIKit requests.

What is cashapelayer?

A layer that draws a cubic Bezier spline in its coordinate space. iOS 3.0+ iPadOS 3.0+ macOS 10.6+ Mac Catalyst 13.1+ tvOS 9.0+

How do I add text in CALayer?

Your UILabel already has a CALayer behind it. If you are putting together several CALayers, you can just add the UILabel's layer as a sublayer of one of those (by using its layer property). If it's direct text drawing in a layer that you want, the UIKit NSString additions that Deepak points to are the way to go.

What is CA layer Swift?

An object that manages image-based content and allows you to perform animations on that content.


2 Answers

re: layer content via -drawRect: or -drawLayer:inContext:

Normallly or always? and this is valid only for view's hosted backing layer or every layer I add as a sublayer to the backing layer?

Always. When these methods are called, the current context will already have the scaling factor applied. For instance, if your layer is 10pt x 10pt, it has a bounds of {0,0,10,10}, regardless of contentsScale. If the contentsScale = 2 (e.g. you are on retina), this is actually 20x20 pixels. CALayer will have already setup a 20x20px backing store and applied a scale factor CGContextScaleCTM(context, 2, 2).

This is so that you can do your drawing in your 10x10 conceptual space either way, but the backing store is doubled if the pixels are doubled.

I'm having difficulties imagining a layer without a view, I can imagine a hierarchy of layers but as soon as I want to display them I should add it as a subhierachy of the view's backing layer, or do exist some other means to use them (like creating and just renderring in a context)?

You can create them, add them as sublayers, and either set their delegate to an object where you implement -drawLayer:inContext:, or you can directly set the layer's contents. On iOS, UIView is a fairly shallow wrapper around a CALayer so it generally makes sense to create subviews instead of directly making sublayers, which is why you aren't so familiar with this usage. On OS X, a layer-backed NSView is a big and complicated wrapper around a CALayer, so it makes a lot more sense to make whole hierarchies of layers within a single view.

What does that actually mean? When I do something like that layer.contents = (id)image.CGImage I need to set the contentsScale.

Yes. But what this passage in the documentation is telling you is that if you want to not look ugly, a 10pt x 10pt layer with contentsScale of 2, needs contents that are a 20px x 20px image. I.e. You need to pay attention to the contentsScale of the layer when you are directly setting the layer contents if you want to avoid scaling artifacts.

Do I also need to set it when my layer draws it's contents calling the delegate method drawLayer:inContext: and the layer isn't the backing layer of a view?

Yes. If you are creating layers yourself (not created via a UIView), you need to set their scale manually. Usually this is just layer.contentsScale = [[UIScreen mainScreen] scale]. (Again, on OS X this is a little more complicated since screens can come and go while running.)

like image 98
gregomni Avatar answered Sep 18 '22 20:09

gregomni


On OS X you basically

  1. grab your window’s -backingScaleFactor
  2. set each (!) layer’s -contentsScale to that scaleFactor
  3. implement -layer:shouldInheritContentsScale:fromWindow: in a controller
  4. set each layer’s -delegate to that controller

I know it’s frowned upon here on SO to just reference an external URL, but it’s hard to describe this. Here is some code that works correctly for me with regard to the scale factors:

https://github.com/JanX2/ScrollingAboutWindow

Hope that helps.

like image 25
JanX2 Avatar answered Sep 20 '22 20:09

JanX2