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

2020-07-14  本文已影响0人  Style_月月

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

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

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

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

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

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

        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)

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


shadow离屏渲染效果

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

        //指定路径 - 避免离屏渲染
        let path = UIBezierPath(rect: btn0.bounds)
        btn0.layer.shadowPath = path.cgPath

效果如下


shadow避免离屏渲染的结果

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

sublayer.contents = (id)[UIImage imageNamed:@"xxx"].CGImage;
[view.layer addSublayer:sublayer];

需要进行裁剪的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;
}
sublayer.contents = (id)[UIImage imageNamed:@"xxx"].CGImage;
[view.layer addSublayer:sublayer];

使用了mask的layer(layer.mask)

优化方案

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

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

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

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

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仔仔仔指出这里的问题)

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

        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)
效果

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

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

label.layer.backgroundColor = aColor
label.layer.cornerRadius = 5

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,都会触发离屏渲染,如图所示

设置了layer的背景色
未设置layer的背景色

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

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

edge antialiasing(抗锯齿)

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

总结

上一篇 下一篇

猜你喜欢

热点阅读