在visionOS中,通过自定义的Core Animation层以多种分辨率提供文本和矢量图像。
概述
如果你的应用直接使用Core Animation层,那么需要更新你的层代码,以便在适当的时候绘制高分辨率版本的内容。SwiftUI和UIKit视图使用Core Animation层来高效地管理界面内容。当视图绘制其内容时,底层的层捕获该内容并缓存它以提高后续的渲染操作。
在大多数Apple平台上,Core Animation以与屏幕相同的分辨率栅格化你的层,但是在visionOS上的Core Animation可以以不同的分辨率栅格化,以最大限度地提高内容清晰度和性能。系统跟随人的眼睛,并以最高可能的分辨率渲染眼前的内容。在这个焦点区域之外,系统以逐步降低的分辨率渲染内容,以减少GPU的工作负载。由于内容处于人的周边视觉中,这些较低的分辨率并不影响内容的清晰度。当人的眼睛在周围移动时,系统会以不同的分辨率重新绘制内容,以匹配焦点的变化。
2倍分辨率下的图像
8倍分辨率下的图像
你可以使用自定义的CALayer对象来提供内容,并且可以配置你的自定义层以支持不同分辨率的绘制。如果你不进行这个额外的配置步骤,每个层将会以@2x的比例因子栅格化其内容,这对于大多数内容来说已经足够好,并且与Retina显示器上的层提供的内容相匹配。然而,如果你选择在不同的分辨率下绘制,那么在visionOS中,层会以最高@8x的比例因子栅格化其内容,这会显著增加文本和基于矢量的内容的细节。
为自定义层请求动态缩放
所有Core Animation层默认都是关闭动态内容缩放的,框架或应用必须明确开启这个支持。如果你的界面只使用SwiftUI或UIKit视图,你不需要做任何事情来支持这个特性。SwiftUI和UIKit会自动为那些从增加的细节中受益的视图开启它,如文本视图和带有SF Symbols或其他基于矢量的艺术作品的图像视图。然而,框架不会为所有的视图开启这个特性,包括UIView和View。
如果你的visionOS界面包括自定义的Core Animation层,你可以为包含基于矢量的内容的任何CALayer对象启用wantsDynamicContentScaling属性。将这个属性设置为true告诉系统你支持以不同的分辨率渲染你的层的内容。然而,这个设置并不保证系统会对你的内容应用动态内容缩放。如果你的层使用不兼容的函数或技术进行绘制,系统可以禁用这个特性。
以下是如何为CATextLayer对象启用这个特性的例子。配置了层之后,将wantsDynamicContentScaling属性设置为true,并将层添加到你的层层次结构中。
let layer = CATextLayer()
layer.string = "Hello, World!"
layer.foregroundColor = UIColor.black.cgColor
layer.frame = parentLayer.bounds
// 将此属性设置为true启用内容缩放
// 并调用setNeedsDisplay重绘层的内容。
layer.wantsDynamicContentScaling = true
parentLayer.addSublayer(layer)
动态内容缩放在层包含文本或基于矢量的内容时效果最好。如果你在你的层中做了以下任何一件事,不要启用这个特性:
- 使用contents属性设置层的内容。
- 主要绘制基于位图的内容。
- 在短时间内反复重绘你的层的内容。
CAShapeLayer类忽略wantsDynamicContentScaling属性的值,并始终启用动态内容缩放。对于其他的Core Animation层,你必须明确启用这个特性才能利用它。
动态绘制层的内容
动态内容缩放要求你使用规定的方法之一绘制你的层的内容。如果你定义了一个CALayer的自定义子类,那么在draw(in:)方法中绘制你的层的内容。如果你使用一个CALayerDelegate对象来绘制层的内容,那么使用代理的drawLayer:inContext:方法。
当你为一个层启用动态内容缩放时,系统会捕获你的应用的绘图命令以供后续播放。当人的眼睛移动时,系统会在有人直接看着它的时候以更高的分辨率绘制层否则以更低的分辨率绘制。因为重绘操作隐含地传达了人正在看什么,所以系统在你的应用的进程之外执行它们。让系统处理这些操作可以保持人的隐私,同时仍然让你的应用享受高分辨率绘图的好处。
一些Core Graphics的例程与动态内容缩放不兼容。即使你为你的层启用了动态内容缩放,如果你的层使用了以下任何一种,系统会自动禁用这个特性:
- Core Graphics shaders。
- 设置意图、质量或其他与位图相关的属性的API。例如,不要调用CGContextSetInterpolationQuality。
- 使用CGBitmapContext来绘制内容。
如果你的应用创建了基于计时器的动画,不要使用你的绘图方法来动画化层的变化。在短时间内反复调用你的层的setNeedsDisplay()会导致系统在短时间内多次绘制层。因为visionOS需要一点额外的时间以高分辨率绘制一个层,每次重绘请求都会强迫它抛弃工作。更好的选择是动画化层基础属性以达到同样的效果,或者在需要时使用CAShapeLayer来动画化路径。
修改层层次结构以提高性能
一个层的后备存储在更高分辨率时比在更低分辨率时需要更多的内存。在启用动态内容缩放前后,测量你的应用的内存使用情况,以确保增加的内存成本是值得的。如果你的应用的内存使用增加太多,限制哪些层采用动态内容缩放。你也可以通过以下方式减少每个层使用的内存量:
- 尽可能地缩小你的层。更大的层需要显著更多的内存,特别是在更高的分辨率下。通过消除填充或额外空间,使层的大小与你的内容的大小匹配。
- 将复杂的内容分隔到不同的层中。而不是在一个单一的层中绘制所有的东西,从多个层构建你的内容,并将它们按层次排列以达到同样的效果。只在真正需要的层中启用动态内容缩放。
- 尽可能使用层属性应用特效。在绘图期间应用特效可能需要你增加层的大小。例如,在绘图时,将比例和旋转效果应用到层的transform属性,而不是在绘图时应用。
- 不要提前在不同的分辨率下绘制你的层的内容并缓存图像。维护多个图像需要更多的内存。如果你确实缓存了图像,只在@2x的比例因子下绘制它们。
- 不要使用你的绘图代码来绘制单一的图像。如果你的层的内容由一个图像组成,直接将那个图像分配给层的contents属性。
复杂的绘图代码也可能导致性能问题。一个包含许多笔触的层可以在较低的比例因子下快速渲染,但在更大的比例因子下可能在计算上太复杂以至于不能渲染。如果一个复杂的层在更高分辨率下无法正确渲染,那么关闭动态内容缩放,并再次测量渲染时间。