iOS 常见触发离屏渲染场景及优化方案总结

简介: iOS 常见触发离屏渲染场景及优化方案总结

以下方案,常用的阴影、圆角等经过笔者测试可行,剩余场景方案仅供参考,并未实际测试


对什么是离屏渲染,以及为什么会产生离屏渲染尚不了解的建议看看四、深入剖析【离屏渲染】原理这篇文章,再来阅读本文


在离屏渲染触发的场景中,按照性能影响从高到低排序,如下所示


  • shadows(阴影)
  • conerRadius > 0 + maskToBounds = true(常见的圆角设置手段)
  • mask(遮罩)
  • allowsGroupOpacity(组不透明)
  • edge antialiasing(抗锯齿)


下面针对不同场景说明为什么以及怎么解决离屏渲染问题


添加了阴影的layer(layer.shadow)



  • layer本身是一块矩形区域,而阴影是作用于在整个非透明区域,并显示在所有layer的最下方。
  • 根据画家算法,由远及近的渲染,阴影是第一个被渲染的,但是阴影渲染有一个前提:我们必须画完所有的layer和子layer后。
  • 所以这时我们就需要一个临时缓存,这个缓存区就是离屏缓冲区,用来将所有layer都渲染完成,再根据所有layer和子layer组合后的图层的形状,添加阴影到FrameBuffer,最后显示到屏幕上


下面我们以按钮的阴影来进行演示

        let btn0 = UIButton(type: .custom)
        btn0.frame = CGRect(x: 100, y: 60, width: 100, height: 100)
        //设置圆角
        self.view.addSubview(btn0)
        //设置背景图片
        btn0.setImage(UIImage(named: "mouse"), for: .normal)
        //阴影
        btn0.layer.shadowColor = UIColor.lightGray.cgColor
        btn0.layer.shadowOpacity = 1.0
        btn0.layer.shadowRadius = 2.0
        btn0.layer.shadowOffset = CGSize(width: 5, height: 5)


根据上面这段代码的运行,可以看到如下结果,设置阴影时是触发了离屏渲染的

微信图片_20220513214216.png


优化方案


使用阴影必须保证 layer 的masksToBounds = false,因此阴影与系统圆角不兼容。但是注意,只是在视觉上看不到,对性能的影响依然。通常使用指定路径来避免离屏渲染


  • 方案1:指定路径
    在上述代码的基础上增加以下两行代码
        //指定路径 - 避免离屏渲染
        let path = UIBezierPath(rect: btn0.bounds)
        btn0.layer.shadowPath = path.cgPath

效果如下

微信图片_20220513214437.png


shadow避免离屏渲染的结果


除了指定路径,还有其他解决方案,不过笔者并没有一一试验,有兴趣的可以自己尝试下


  • 利用混合图层模拟阴影的效果
sublayer.contents = (id)[UIImage imageNamed:@"xxx"].CGImage;
[view.layer addSublayer:sublayer];


  • 利用一个阴影效果的视图添加在需要显示阴影的位置
  • 使用 Core Graphics 绘制阴影


需要进行裁剪的layer(layer.masksToBounds / view.clipsToBounds)


这种场景就是我们常用的圆角处理,当我们需要绘制一个带有圆角并且需要剪切圆角以外内容的容器时,就会触发离屏渲染,例如UIButton、UIImageView等

注意:iOS官方针对UIImageView有一些优化,

==> 在iOS9之前,UIImageView和UIButton通过cornerRadius+masksToBounds设置圆角都会触发离屏渲染,

==> 但是UIImageView在ios9以后,针对UIImageView中的image设置圆角并不会触发离屏渲染,如果加上了背景色或者阴影等其他效果还是会触发离屏渲染的


优化方案


对于content无内容或者内容非背景透明(不涉及到圆角以外的区域)的layer,直接设置layer的backgroundColor + cornerRadius 属性绘制圆角


常用的优化方案参见iOS 常用的圆角处理方式总结


下面再补充一些其他方案


  • 后台绘制,前台设置图片
- (void)setCircleImage
{
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        UIImage * circleImage = [image imageWithCircle];
        dispatch_async(dispatch_get_main_queue(), ^{
            imageView.image = circleImage;
        });
    });
}
#import "UIImage+Addtions.h"
@implementation UIImage (Addtions)
//返回一张圆形图片
- (instancetype)imageWithCircle
{
    UIGraphicsBeginImageContextWithOptions(self.size, NO, 0);
    UIBezierPath *path = [UIBezierPath bezierPathWithOvalInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
    [path addClip];
    [self drawAtPoint:CGPointZero];
    UIImage *image = UIGraphicsGetImageFromCurrentImageContext();
    UIGraphicsEndImageContext();
    return image;
}
  • 使用混合图层,在layer上方叠加相应mask形状的半透明layer
sublayer.contents = (id)[UIImage imageNamed:@"xxx"].CGImage;
[view.layer addSublayer:sublayer];


使用了mask的layer(layer.mask)


  • mask是覆盖在所有layer及其子layer之上的,可能还带有一定的透明度。
  • mask也是需要等整个layer树绘制完成,再加上mask和组合后的lzyer进行组合,所以需要开辟一个独立于FrameBuffer的内存,用于将layer及其子layer画完,最后再和mask进行组合,存储到FrameBuffer,视频控制器从FrameBuffer中读取数据显示到屏幕上


优化方案


  • 不使用mask,使用混合图层,在layer上方叠加相应mask形状的半透明layer


设置了组透明度为 YES,并且透明度不为 1 的 layer (layer.allowsGroupOpacity/ layer.opacity)


  • group opacity中alpha并不是分别应用到每一层之上,需要整个layer树画完之后,在统一加上alpha,和底层其他layer的像素进行组合,此时显然无法通过一次遍历就得到结果
  • 需要另外开启一个独立内存,先将layer及其子layer画好,最后给组合后的图层加上alpha进行渲染,将最终结果存储到帧缓冲区
  • GroupOpacity 开启离屏渲染的条件是:layer.opacity != 1.0并且有子 layer 或者背景图。


优化方案


关闭allowsGroupOpacity属性,根据产品需求自己控制layer透明度


采用了光栅化的 layer (layer.shouldRasterize)


如果layer的layer.shouldRasterize被设置为true,会在触发离屏渲染的同时,将光栅化后的内容缓存起来,如果在下一次,对应的layer和子layer没有改变,则复用离屏缓冲区的结果,可以很大程度提升性能


  • 当视图内容是静态不变时,设置 shouldRasterize(光栅化)为YES,此方案最为实用方便。
view.layer.shouldRasterize = true;
view.layer.rasterizationScale = view.layer.contentsScale;


  • 但当视图内容是动态变化(如后台下载图片完毕后切换到主线程设置)时,使用此方案反而为增加系统负荷。


绘制了文字的 layer (UILabel, CATextLayer, Core Text 等)


想要在 UILabel 和 UITextView 上实现低成本的圆角(不触发离屏渲染),需要保证 layer 的contents呈现透明的背景色,文本视图类的 layer 的contents默认是透明的(字符就在这个透明的环境里绘制、显示),此时只需要设置 layer 的backgroundColor,再加上cornerRadius就可以搞定了。不过 UILabel 上设置backgroundColor的行为被更改了,不再是设定 layer 的背景色而是为contents设置背景色,UITextView 则没有改变这一点

不要直接利用label.backgroundColor = aColor 设置背景色
-不要直接在XIB中为label设置背景色
(感谢小K仔仔仔指出这里的问题)


在这里重新做下梳理,经过测试


  • 设置label.backgroundColor + cornerradius ,圆角的裁剪并没有label设置的backgroundColor上,这种背景颜色的设置方式其实是为contents设置背景色,而contents圆角的裁剪需要设置 masksToBounds 才会生效。但是这种方式也并没有触发离屏渲染,
        let label = UILabel(frame: CGRect(x: 100, y: 100, width: 100, height: 50))
        label.text = "测试"
        label.backgroundColor = UIColor.lightGray
        label.layer.cornerRadius = 8
        label.layer.masksToBounds = true
        self.view.addSubview(label)

微信图片_20220513215418.png

==> 猜测原因可能是这样的,我们设置的背景色只是为contents设置的,所以圆角的裁剪其实针对的也只是contents,相当于此时只有一个图层需要设置圆角,所以并不会触发离屏渲染


  • label中设置label3.layer.backgroundColor + cornerradius,此时也不会触发离屏渲染,原因同一种情况类似


UILabel中不会触发离屏渲染的圆角化方案


  • UILabel直接通过label的layer设置背景色 + cornerRadius
label.layer.backgroundColor = aColor
label.layer.cornerRadius = 5


  • label3.backgroundColor + cornerradius + masksToBounds


UILabel哪些情况会触发离屏渲染?


大致测试了下,有以下两种情况


  • UILabel中添加了子视图,且需要圆角化,类似下面这种
let label3 = UILabel(frame: CGRect(x: 100, y: 350
            , width: 100, height: 50))
       label3.text = "测试3"
       let view3 = UIView(frame: CGRect(x: 0, y: 0, width: 20, height: 20))
       view3.backgroundColor = UIColor.red
       label3.addSubview(view3)
        label3.layer.backgroundColor = UIColor.lightGray.cgColor
       label3.layer.cornerRadius = 8
       label3.layer.masksToBounds = true
       self.view.addSubview(label3)

此时不论是否设置label3.layer.backgroundColor,都会触发离屏渲染,如图所示

微信图片_20220513215929.png


  • 设置label.layer.backgroundColor + cornerradius时,同时设置了 masksToBounds,也会触发离屏渲染,所以在写代码时,要注意,label不复杂时,仅设置
    label.layer.backgroundColor + cornerradius即可圆角化。


使用高斯模糊(毛玻璃)效果


ios屏幕显示推送通知页面或者UIVisualEffectView


edge antialiasing(抗锯齿)


不设置 allowsEdgeAntialiasing 属性为YES(默认为NO)


总结


  • RoundedCorner(圆角) 在仅指定cornerRadius时不会触发离屏渲染,仅适用于特殊情况:contents为 nil 或者contents不会遮挡背景色时的圆角。
  • Shawdow 可以通过指定路径来取消离屏渲染。
  • Mask 无法取消离屏渲染,可以利用混合图层来进行优化。


相关文章
|
9月前
|
存储 数据建模 数据库
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
IOS开发数据存储:什么是 UserDefaults?有哪些替代方案?
146 0
|
3月前
|
传感器 iOS开发 UED
探索iOS生态系统:从App Store优化到用户体验提升
本文旨在深入探讨iOS生态系统的多个方面,特别是如何通过App Store优化(ASO)和改进用户体验来提升应用的市场表现。不同于常规摘要仅概述文章内容的方式,我们将直接进入主题,首先介绍ASO的重要性及其对开发者的意义;接着分析当前iOS平台上用户行为的变化趋势以及这些变化如何影响应用程序的设计思路;最后提出几点实用建议帮助开发者更好地适应市场环境,增强自身竞争力。
|
3月前
|
安全 Android开发 iOS开发
深入探索iOS与Android系统的差异性及优化策略
在当今数字化时代,移动操作系统的竞争尤为激烈,其中iOS和Android作为市场上的两大巨头,各自拥有庞大的用户基础和独特的技术特点。本文旨在通过对比分析iOS与Android的核心差异,探讨各自的优势与局限,并提出针对性的优化策略,以期为用户提供更优质的使用体验和为开发者提供有价值的参考。
|
6月前
|
测试技术 Linux 虚拟化
iOS自动化测试方案(五):保姆级VMware虚拟机安装MacOS
详细的VMware虚拟机安装macOS Big Sur的保姆级教程,包括下载VMware和macOS镜像、图解安装步骤和遇到问题时的解决方案,旨在帮助读者顺利搭建macOS虚拟机环境。
277 3
iOS自动化测试方案(五):保姆级VMware虚拟机安装MacOS
|
6月前
|
测试技术 开发工具 iOS开发
iOS自动化测试方案(三):WDA+iOS自动化测试解决方案
这篇文章是iOS自动化测试方案的第三部分,介绍了在没有MacOS系统条件下,如何使用WDA(WebDriverAgent)结合Python客户端库facebook-wda和tidevice工具,在Windows系统上实现iOS应用的自动化测试,包括环境准备、问题解决和扩展应用的详细步骤。
582 1
iOS自动化测试方案(三):WDA+iOS自动化测试解决方案
|
6月前
|
测试技术 数据安全/隐私保护 iOS开发
iOS自动化测试方案(四):保姆级搭建iOS自动化开发环境
iOS自动化测试方案的第四部分,涵盖了基础环境准备、iPhone虚拟机设置、MacOS虚拟机与iPhone真机的连接,以及扩展问题和代码示例,确保读者能够顺利完成环境搭建并进行iOS自动化测试。
663 0
iOS自动化测试方案(四):保姆级搭建iOS自动化开发环境
|
6月前
|
测试技术 虚拟化 iOS开发
iOS自动化测试方案(二):Xcode开发者工具构建WDA应用到iphone
这篇文章是iOS自动化测试方案的第二部分,详细介绍了在Xcode开发者工具中构建WebDriverAgent(WDA)应用到iPhone的全过程,包括环境准备、解决构建过程中可能遇到的错误,以及最终成功安装WDA到设备的方法。
356 0
iOS自动化测试方案(二):Xcode开发者工具构建WDA应用到iphone
|
6月前
|
测试技术 开发工具 虚拟化
iOS自动化测试方案(一):MacOS虚拟机保姆级安装Xcode教程
这篇文章提供了一份保姆级的教程,指导如何在MacOS虚拟机上安装Xcode,包括环境准备、基础软件安装以及USB扩展插件的使用,以实现iOS自动化测试方案的第一步。
346 0
iOS自动化测试方案(一):MacOS虚拟机保姆级安装Xcode教程
|
9月前
|
缓存 开发工具 iOS开发
优化iOS中Objective-C代码调起支付流程的速度
优化iOS中Objective-C代码调起支付流程的速度
149 2
|
9月前
|
移动开发 安全 数据安全/隐私保护
ios安全加固 ios 加固方案
ios安全加固 ios 加固方案
122 1
ios安全加固 ios 加固方案

热门文章

最新文章