iOS圆角的离屏渲染,你真的弄明白了吗

测试环境

Xcode 11.5
iPhone 11 Pro Simulator
iOS 13.5

  1. 如何设置圆角才会触发离屏渲染

我们经常看到,圆角会触发离屏渲染。但其实这个说法是不准确的,因为圆角触发离屏渲染也是有条件的!
我们先来看看苹果官方文档对于cornerRadius的描述:

Setting the radius to a value greater than 0.0 causes the layer to begin drawing rounded corners on its background. By default, the corner radius does not apply to the image in the layer’s contents property; it applies only to the background color and border of the layer. However, setting the masksToBounds property to true causes the content to be clipped to the rounded corners.

我们发现设置cornerRadius大于0时,只为layer的backgroundColor和border设置圆角;而不会对layer的contents设置圆角,除非同时设置了layer.masksToBounds为true(对应UIView的clipsToBounds属性)。

如果这时,你认为layer.masksToBounds或者clipsToBounds设置为true就会触发离屏渲染,这是不完全正确的。

我们先打开模拟器的离屏渲染颜色标记:

  • 不设置layer.masksToBounds或者clipsToBounds,其默认值为NO
- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
    // 设置背景色
    view1.backgroundColor = UIColor.redColor;
    // 设置边框宽度和颜色
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;
    // 设置圆角
    view1.layer.cornerRadius = 100.0;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}

我们看到只有背景色、边框以及圆角的时候,确实不会触发离屏渲染。

  • 设置layer.masksToBounds或者clipsToBounds为YES
- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
    // 设置背景色
    view1.backgroundColor = UIColor.redColor;
    // 设置边框宽度和颜色
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;
    // 设置圆角
    view1.layer.cornerRadius = 100.0;

    // 设置裁剪
    view1.clipsToBounds = YES;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}

当我们开启layer.masksToBounds或者clipsToBounds时,同样的没有触发离屏渲染。这是因为我们还没有设置图片。

  • 设置layer.masksToBounds或者clipsToBounds为YES,同时设置图片
- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
    // 设置背景色
    view1.backgroundColor = UIColor.redColor;
    // 设置边框宽度和颜色
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;

    //设置图片
    view1.layer.contents = (__bridge id)[UIImage imageNamed:@"pkq"].CGImage;

    // 设置圆角
    view1.layer.cornerRadius = 100.0;
    // 设置裁剪
    view1.clipsToBounds = YES;
    view1.center = self.view.center;
    [self.view addSubview:view1];
}

当我们开启layer.masksToBounds或者clipsToBounds时,同时设置图片时,就会触发离屏渲染。

  • 其实不光是图片,我们为视图添加一个有颜色、内容或边框等有图像信息的子视图也会触发离屏渲染。

有图像信息还包括在视图或者layer的draw方法中进行绘制等。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
    // 设置背景色
    view1.backgroundColor = UIColor.redColor;
    // 设置边框宽度和颜色
    view1.layer.borderWidth = 2.0;
    view1.layer.borderColor = UIColor.blackColor.CGColor;
    // 设置圆角
    view1.layer.cornerRadius = 100.0;
    // 设置裁剪
    view1.clipsToBounds = YES;

    // 子视图
    UIView *view2 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 100.0, 100.0)];
    // 下面3个任何一个属性
    // 设置背景色
    view2.backgroundColor = UIColor.blueColor;
    // 设置内容
    view2.layer.contents = (__bridge id)([UIImage imageNamed:@"pkq"].CGImage);
    // 设置边框
    view2.layer.borderWidth = 2.0;
    view2.layer.borderColor = UIColor.blackColor.CGColor;
    [view1 addSubview:view2];

    view1.center = self.view.center;
    [self.view addSubview:view1];
}

  1. 圆角触发离屏渲染的真正原因

图层的叠加绘制大概遵循“画家算法”。

油画算法:先绘制场景中的离观察者较远的物体,再绘制较近的物体。

先绘制红色部分,再绘制⻩色部分,最后再绘制灰⾊部分,即可解决隐藏面消除的问题。即将场景按照物理距离和观察者的距离远近排序,由远及近的绘制即可。

当我们设置了cornerRadius以及masksToBounds进行圆角+裁剪时,masksToBounds裁剪属性会应用到所有的图层上。

本来我们从后往前绘制,绘制完一个图层就可以丢弃了。但现在需要依次在 Offscreen Buffer中保存,等待圆角+裁剪处理,即引发了 离屏渲染

  • 背景色、边框、背景色+边框,再加上圆角+裁剪,根据文档说明,因为 contents = nil 没有需要裁剪处理的内容,所以masksToBounds设置为YES或者NO都没有影响。
  • 一旦我们 为contents设置了内容 ,无论是图片、绘制内容、有图像信息的子视图等,再加上圆角+裁剪,就会触发离屏渲染。

不一定是直接为contents赋值!

  1. iOS9及以后的优化

关于圆角,iOS 9及之后的系统版本,苹果进行了一些优化。

  • layer.contents/imageView.image

我们只设置contents或者UIImageView的image,并加上圆角+裁剪,是不会产生离屏渲染的。但如果加上了背景色、边框或其他有图像内容的图层,还是会产生离屏渲染。

- (void)viewDidLoad {
    [super viewDidLoad];
    UIView *view1 = [[UIView alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
    //设置图片
    view1.layer.contents = (__bridge id)[UIImage imageNamed:@"qiyu"].CGImage;
    // 设置圆角
    view1.layer.cornerRadius = 100.0;
    // 设置裁剪
    view1.clipsToBounds = YES;

    view1.center = self.view.center;
    [self.view addSubview:view1];
}

其实这也是可以理解的,因为只有 单层 内容需要添加圆角和裁切,所以可以不需要用到离屏渲染技术。但如果加上了背景色、边框或其他有图像内容的图层,就会产生为 多层 添加圆角和裁切,所以还是会触发离屏渲染(如1中的第3个例子)。

所以,我们在使用类似于UIButton的视图的时候需要注意:

  • UIButton
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];
    // 创建一个button视图
    UIButton *button = [[UIButton alloc] initWithFrame:CGRectMake(0, 0, 200.0, 200.0)];
    //设置图片
    [button setImage:[UIImage imageNamed:@"pkq"] forState:UIControlStateNormal];
    button.center = self.view.center;
    [self.view addSubview:button];
}

我们为UIButton设置一个图片,其实会添加一个UIImageView。

  • 为UIButton添加圆角和裁剪,则会触发离屏渲染。
// 设置圆角
button.layer.cornerRadius = 100.0;
// 设置裁剪
button.clipsToBounds = YES;

  • 如果改为UIButton中的UIImageView添加圆角和裁剪,则 不会触发离屏渲染。
// 设置圆角
button.imageView.layer.cornerRadius = 100.0;
// 设置裁剪
button.imageView.clipsToBounds = YES;

扩展阅读

更多渲染问题可以看下面这篇文章。

iOS 渲染原理解析 https://mp.weixin.qq.com/s/6ckRnyAALbCsXfZu56kTDw


https://mp.weixin.qq.com/s/RFIiwvEhdSBub7HFm3C1Ug

iOS APP 图标版本化

在我们的项目开发过程中,需要频繁打包给测试人员去测试,有时候我们都不知道测试机上安装的版本是否是最新的,这样会造成很多不必要的麻烦和成本。因此我们需要将buildNumber以水印的方式打在APPIcon上,可以很直观的知道当前是哪一个版本。

发布于:8天以前  |  21次阅读  |  详细内容 »

如何实现一个HTTP请求库——axios源码阅读与分析

在前端开发过程中,我们经常会遇到需要发送异步请求的情况。而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率。

发布于:9天以前  |  23次阅读  |  详细内容 »

老司机 iOS 周报 #144 | 2021-01-14

发布于:12天以前  |  44次阅读  |  详细内容 »

快手,快影 iOS App反调试

发布于:19天以前  |  65次阅读  |  详细内容 »

优酷iOS插件化页面架构方法

随着业务不停地迭代,优酷 APP 用于分发视频资源的 UI 控件越写越多,也越来越复杂,并且同时相似相近的代码也非常多。

发布于:2月以前  |  180次阅读  |  详细内容 »

iOS中的内嵌汇编

写一篇在iOS上使用汇编的文章的想法在脑袋里面停留了很久了,但是迟迟没有动手。虽然早前在做启动耗时优化的工作中,也做过通过拦截objc_msgSend并插入汇编指令来统计方法调用耗时的工作,但也只仅此而已。刚好最近的时间项目在做安全加固,需要写更多的汇编来提高安全性(文章内汇编使用指令集为ARM64),也就有了本文

发布于:2月以前  |  182次阅读  |  详细内容 »

77.9K 的 Axios 项目有哪些值得借鉴的地方

Axios 是一个基于 Promise 的 HTTP 客户端,同时支持浏览器和 Node.js 环境。它是一个优秀的 HTTP 客户端,被广泛地应用在大量的 Web 项目中。

发布于:2月以前  |  166次阅读  |  详细内容 »

不会吧,这也行?iOS后台锁屏监听摇一摇

一般情况下,出于省电、权限、合理性等因素考虑,给人的感觉是很多奇怪的需求安卓可以实现,但是iOS就无法实现!今天要介绍的需求也有这种感觉,就是“当 APP 处于后台或锁屏状态时,依旧可以监听到摇一摇,进而触发某些功能,比如:语音播报”。

发布于:3月以前  |  306次阅读  |  详细内容 »

iOS 稳定性:App 被终止的原因

本次 session 主要内容如下: 介绍了后台应用终止的常见原因,并提供了一些优化建议 介绍了 MetricsKit 提供的在代码中获取诊断和性能数据的方法 介绍了 Xcode Metrics Ogranizer 提供的关于线上用户性能数据的可视化报告

发布于:3月以前  |  421次阅读  |  详细内容 »

优酷iOS插件化页面架构方法

随着业务不停地迭代,优酷 APP 用于分发视频资源的 UI 控件越写越多,也越来越复杂,并且同时相似相近的代码也非常多。

发布于:3月以前  |  318次阅读  |  详细内容 »

Vue中Axios的封装和API接口的管理

在vue项目中,和后台交互获取数据这块,我们通常使用的是axios库,它是基于promise的http库,可运行在浏览器端和node.js中。他有很多优秀的特性,例如拦截请求和响应、取消请求、转换json、客户端防御XSRF等。所以我们的尤大大也是果断放弃了对其官方库vue-resource的维护,直接推荐我们使用axios库。如果还对axios不了解的,可以移步axios文档。

发布于:3月以前  |  292次阅读  |  详细内容 »

iOS 持续集成:更完备的 App Store Connect API

时隔两年 App Store Connect API 有了更新,WWDC 2018 推出了 App Store Connect API ,用于自动化一些 App Store Connect 后台操作。这次更新包含了 app 元数据相关的API,补上了原来缺失的重要一环, 使得几乎可以通过 App Store Connect API 完成 App Store Connect 上的所有操作。今后开发、证书配置、用户管理、测试、发布全流程都可以通过 API 完成。

发布于:3月以前  |  354次阅读  |  详细内容 »

iOS 性能优化:优化 App 启动速度

苹果是一家特别注重用户体验的公司,过去几年一直在优化 App 的启动时间,特别是去年的 WWDC 2019 keynote[1] 上提到,在过去一年苹果开发团队对启动时间提升了 200%

发布于:3月以前  |  327次阅读  |  详细内容 »

iOS圆角的离屏渲染,你真的弄明白了吗

发布于:3月以前  |  307次阅读  |  详细内容 »

iOS导航栏整体滑动解决方案(类似淘宝)

发布于:3月以前  |  359次阅读  |  详细内容 »

让你的应用远离越狱:iOS 14 App Attest 防护功能

当越狱在 iOS 设备第一次流行起来时,iOS 开发人员会尝试各种方法来保护自己的应用程序,以让应用免受盗版等不确定因素的困扰。有许多方法可以做到这一点,包括检查 Cydia 是否存在、检测应用程序是否可读取自身沙箱之外的文件、在检测到调试器时让应用程序崩溃等等。

发布于:3月以前  |  346次阅读  |  详细内容 »

探秘 iOS 14 的 WidgetKit

Widget Extension 提供了 small, medium, large 三个尺寸,不同尺寸可以展示不同的数据、不同的界面,开发者也可以锁定自己APP的 Widget 只有某类尺寸,相同的widget也能重复添加。作为添加在主屏幕上的控件,苹果用了 “At a glance” 来形容 widget ,所以 widget extension 是无法交互的,它能做的只有展示一些信息与点击两个作用,点击后就会引导至app,同时为了性能与耗电量的考虑,Widget extension 也不能展示视频和动态图像。

发布于:3月以前  |  383次阅读  |  详细内容 »

iOS 的自动构建流程

发布于:3月以前  |  410次阅读  |  详细内容 »

iOS14 Widget 万字指北,先人一步获得顶级流量

2020 年 6 月 22 日,苹果召开了第一次线上的开发者大会 - WWDC20。这次发布会上宣布了ARM架构Mac芯片(拳打Intel)、iOS 14 ATT(脚踢Facebook),可谓是一次载入史册(我是爸爸)的发布会了,当然还发布了被称为下一个顶级流量入口的Widget。踩着八月的尾巴,本次我们就来探究一下Widget。本文会从Widget初窥和Widget开发两个维度和章节来探究一下Widget, 其中初窥章节会带您简单的了解一下Widget,适合应用决策者阅读; 开发章节会带着您一步一步的完成设计开发Widget,适合程序员阅读。

发布于:3月以前  |  523次阅读  |  详细内容 »

iOS 性能优化 - Allocations分析内存分配

发布于:3月以前  |  375次阅读  |  详细内容 »

最多阅读

快速配置 Sign In with Apple 1年以前  |  3784次阅读
开篇 关于iOS越狱开发 1年以前  |  2565次阅读
APP适配iOS11 1年以前  |  2526次阅读
使用 GPUImage 实现一个简单相机 1年以前  |  2509次阅读
给数组NSMutableArray排序 1年以前  |  2487次阅读
在越狱的iPhone设置上使用lldb调试 1年以前  |  2440次阅读
App Store 审核指南[2017年最新版本] 1年以前  |  2336次阅读
UITableViewCell高亮效果实现 1年以前  |  2316次阅读
所有iPhone设备尺寸汇总 1年以前  |  2258次阅读
使用ssh访问越狱iPhone的两种方式 1年以前  |  2193次阅读
关于Xcode不能打印崩溃日志 1年以前  |  2117次阅读
使用ssh 访问越狱iPhone的两种方式 1年以前  |  2014次阅读
UIDevice的简单使用 1年以前  |  1805次阅读
为对象添加一个释放时触发的block 1年以前  |  1783次阅读
使用最高权限操作iPhone手机 1年以前  |  1749次阅读