什么时候需要设置CALayer的contentsScale属性?

32
文档中,苹果公司指出,当一个层创建额外的带有不同比例因子的核心动画层并将它们合成到自己的内容中时,需要考虑该层的比例因子。同时,当一个核心动画层直接设置 contents 属性时也需要考虑比例因子。但最后一句话我理解得不是很清楚。
此外,苹果还说:
“直接使用核心动画层提供内容的应用程序可能需要调整其绘制代码以考虑比例因子。通常情况下,当您在视图的 drawRect: 方法或 layer 的代理的 drawLayer:inContext: 方法中进行绘制时,系统会自动调整图形上下文以考虑比例因子。”
这意味着绘制代码是否需要调整取决于您在哪里进行绘制: 是否是视图的 drawRect: 方法或 layer 代理的 drawLayer:inContext: 方法。但这个自动调整只适用于视图的主备用层,对于每个添加到主备用层作为子层的其他层是否也适用不太清楚。苹果也说:
“如果您的应用程序创建不带关联视图的层,则每个新建的层对象的比例因子最初设置为 1.0。如果您不更改该比例因子,并且随后在高分辨率屏幕上绘制该层,则该层的内容会自动缩放以补偿比例因子的差异。如果您不希望内容被缩放,可以将该层的比例因子更改为 2.0,但如果您这样做没有提供高分辨率内容,则现有内容可能会比预期更小。要解决该问题,需要为该层提供更高分辨率的内容。”我很难想象一个没有视图的图层,我可以想象一个图层的层次结构,但一旦我想要显示它们,我应该将它们作为视图的后备图层的子层次添加进去,或者存在其他使用它们的方法吗(例如创建并在上下文中渲染)?
核心动画的合成引擎查看每个图层的contentsScale属性,以确定该图层的内容是否需要在合成过程中进行缩放。如果您的应用程序创建没有关联视图的图层,则每个新图层对象的比例因子最初设置为1.0。如果您不更改该比例因子,并且随后在高分辨率屏幕上绘制该图层,则图层的内容会自动缩放以补偿比例因子的差异。如果您不希望内容被缩放,您可以将图层的比例因子更改为2.0,但是如果您这样做而没有提供高分辨率内容,则现有内容可能会比您预期的要小。要解决此问题,您需要为图层提供更高分辨率的内容。
那实际上是什么意思呢?当我像这样做layer.contents = (id)image.CGImage时,我需要设置contentsScale。
当我的图层通过调用委托方法drawLayer:inContext:绘制其内容,并且该图层不是视图的后备图层时,我是否还需要设置它?

在 iOS 的情况下,这里有详细的解释:https://dev59.com/2afja4cB1Zd3GeqPsTnI#47760444 和 https://stackoverflow.com/a/47778332/294884 希望对某些人有所帮助。 - Fattie
3个回答

18

关于通过-drawRect:或-drawLayer:inContext:绘制层内容:

通常或总是这样吗?这仅适用于托管后备图层的视图还是每个我添加到后备图层的子层?

总是。当调用这些方法时,当前上下文已经应用了缩放因子。例如,如果您的图层为10pt x 10pt,则其边界为{0,0,10,10},而不考虑contentsScale。如果contentsScale = 2(例如,您在retina上),则实际上为20x20像素。CALayer将已经设置一个20x20px的后备存储器并应用比例因子CGContextScaleCTM(context, 2, 2)。

这样做是为了让您可以以任何方式在您的10x10概念空间中进行绘制,但是如果像素加倍,那么后备存储器就会加倍。

我很难想象没有视图的图层,我可以想象层次结构,但是一旦我想要显示它们,我应该将其作为子层次添加到视图的后备图层中,还是存在其他使用方法(例如创建并直接在上下文中呈现)?

您可以创建它们,将它们添加为子层,并将其委托设置为实现-drawLayer:inContext:的对象,或者您可以直接设置图层的内容。在iOS上,UIView是一个相当浅的包装器,包装了CALayer,因此通常创建子视图而不是直接创建子层,这就是为什么您不太熟悉此用法的原因。在OS X上,基于图层的NSView是一个大而复杂的包装器,包含了许多CALayer,因此在单个视图内创建整个层次结构更有意义。

那到底是什么意思呢?当我像layer.contents = (id)image.CGImage那样做时,我需要设置contentsScale吗?

这意味着您需要为该图层设置正确的contentsScale以匹配与您正在使用的图像相同的缩放级别。例如,如果您的图像是retina分辨率的,则应将contentsScale设置为2.0,否则图像将被绘制为两倍。

是的。但是文档中这段话告诉你的是,如果你希望不显得丑陋,一个内容比例为2的10pt x 10pt层需要20px x 20px的图像作为内容。也就是说,如果你想避免缩放伪影,直接设置层内容时需要注意内容的比例。

如果我的层通过调用委托方法drawLayer:inContext:绘制它的内容,并且该层不是视图的后备层,我还需要设置它吗?

是的。如果您自己创建图层(而不是通过UIView创建),则需要手动设置它们的比例。通常只需使用layer.contentsScale = [[UIScreen mainScreen] scale]。(再次注意,在OS X上这会稍微复杂一些,因为屏幕可能在运行时出现并消失。)


感谢您提供详细的答案!如果A和B都是Z层的子节点,那么Z层是否也需要设置contentsScale呢?或者说父节点的contentsScale是否无关紧要? - Crashalot

7
在OS X上,你基本上需要:
  1. 获取窗口的 -backingScaleFactor
  2. 将每个图层的 -contentsScale 设置为该比例因子
  3. 在控制器中实现 -layer:shouldInheritContentsScale:fromWindow:
  4. 将每个图层的 -delegate 设置为该控制器
我知道在这里仅仅引用一个外部URL是不被允许的,但是很难描述这个问题。这里有一些代码能正确地处理比例因子:https://github.com/JanX2/ScrollingAboutWindow 希望这能帮到你。

一个有效的替代方案是在viewDidChangeBackingProperties(或windowDid...)中设置图层(或图层)的contentsScale,这样可以吗? - Peter Hosey
没有头绪。据我所知,这是苹果告诉你如何使用的方式。 - JanX2

6

尽管图层几乎总是用于视图中,但该图层可能已作为另一个图层的子图层或分配为另一个图层的掩码而被特别创建并添加。在这两种情况下,都必须设置contentsScale属性。例如:

UIView *myMaskedView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];
CALayer *myMaskLayer = [CALayer new];
myMaskLayer.frame = myMaskedView.bounds;
UIImage *maskImage = [UIImage imageNamed:@"some_image"];
myMaskLayer.contents = (id)maskImage.CGImage;
myMaskLayer.contentsScale = maskImage.scale;
myMaskedView.layer.mask = myMaskLayer;

您可能还希望建立分层结构来执行动画,而不使用每个图层来支持视图:

UIView *myView = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100, 100)];

CALayer *sublayerA = [CALayer new];
sublayerA.contentsScale = myView.layer.contentsScale;
[myView.layer addSublayer:sublayerA];

CALayer *sublayerB = [CALayer new];
sublayerB.contentsScale = myView.layer.contentsScale;
[myView.layer addSublayer:sublayerB];

// Continue adding layers   

这对Retina设备尤其重要,它们具有本地屏幕比例为2.0;默认比例为1.0的图层会显示明显的像素化。


啊哈!这并不适用于自己创建的图层,这就解释了为什么我的 Mac 应用程序托管的图层没有正确设置 contentsScale。我理解如果我的视图是支持图层而不是托管图层,那么当它创建其图层时,它将由视图(在 NSView 级别)为我设置。 - Peter Hosey
我认为这个回答和评论解决了我在阅读这个问题时遇到的疑问:当您创建一个图层,然后将其添加到附加到视图的图层层次结构中时,添加图层的操作本身并不会将contentScale属性设置为屏幕的比例,对吗? - s73v3r
1
正确的。向视图层次结构添加一个图层不会设置其“contentsScale”。 - Austin
如果A和B都是Z层的子节点,那么Z层是否也需要设置contentsScale呢?还是说父节点的contentsScale并不重要? - Crashalot
是的!'sublayerA.contentsScale = myView.layer.contentsScale;' 绝对是最好、最优雅的解决方案。只需一行代码,我的图层从模糊和不清晰变得锐利和酷炫。 - Elise van Looij
终于有一个对我真正有效的答案了!!!我已经疯了,到处都放置TheLayer.contentScale....谢谢! - JavaZava

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接