圆角 & 离屏渲染实践
下文主要针对各种加圆角的情况进行实践,看看会不会出现离屏渲染的情况。
测试条件
- 测试平台:iPhone 12,iOS 16.3.1,Xcode 14.2。
- 测试方法:运行项目后,通过 Xcode 设置 “Debug -> View Debugging -> Rendering -> Color Off-screen Rendered” 来打开离屏渲染检测。
- 主要测试点:
- 是否开启
masksToBounds
- 是否设置图片
- 是否设置背景色”
- 父视图是否具有透明度
- 是否开启
结论
这里先把结论放到最前面,后面您可以跳过实践过程,直接切到解决方案一节。
✅ 代表不会触发离屏渲染;❌ 代表会触发离屏渲染。
UIView | UIImageView | UIButton(只有文字) | UIButton(图片/背景图) | |
---|---|---|---|---|
直接设置 | ✅ | ✅ | ✅ | ❌ |
背景色(非透明) | ✅ | ❌ | ✅ | ❌ |
父视图具有透明度(alpha ) |
❌ | ❌ | ❌ | ❌ |
下面重点说一下 UIImageView
和 UIButton
的测试过程:
UIImageView
iOS 9 对 UIImageView
进行了一系列优化,相比较过去而言,触发离屏渲染的场景要小了不少
直接设置
lazy var testView = UIImageView().then {
$0.image = UIImage(named: "Forms/share-cover")
$0.layer.masksToBounds = true
$0.layer.cornerRadius = 30
}
一个设置了图片的 UIImageView。示例中同时设置了 masksToBounds
和 cornerRadius
。从图片上可以看出来,并不会触发离屏渲染:
UIImageView 不设置
masksToBounds
,只设置cornerRadius
的话不会显示圆角。
添加背景色
那如果我们手贱再给它添加一个背景色呢?
lazy var testView = UIImageView().then {
$0.image = UIImage(named: "Forms/share-cover")
$0.layer.masksToBounds = true
$0.layer.cornerRadius = 30
$0.backgroundColor = .white // 添加一个白色的背景色
}
果然手贱是没有好处的,设置了背景色后则会触发离屏渲染。
UIButton
UIButton
默认也是没有背景色的,需要同时考虑文字和图片两种情况。
文字
如果不设置背景色的话,只有文字的 UIButton
对象是显示不出来圆角的,不过我也是分别测了一下。
lazy var testView = UIButton(type: .custom).then {
$0.setTitle("这是一个标题", for: .normal)
$0.layer.cornerRadius = 30
}
结论是:不论设置不设置背景色,只有文字的 UIButton
对象添加圆角后都不会触发离屏渲染。
同时只需要设置 cornerRadius
,不需要开启 masksToBounds
就可以显示出来圆角。
图片 & 背景图
因为现象一致,所以图片和背景图在这里归位一类进行讨论。
带图片的 UIButton
是重点测试对象,分下面几种情况:
直接设置
lazy var testView = UIButton(type: .custom).then {
$0.setTitle("这是一个标题", for: .normal)
$0.layer.masksToBounds = true
$0.layer.cornerRadius = 30
$0.setImage(UIImage(named: "Forms/share-cover"), for: .normal)
}
如果
UIButton
只设置cornerRadius
而不开启masksToBounds
,那么图片是不会显示出圆角的。
从图上看,是会触发离屏渲染。
尝试解决离屏渲染问题
实践了哪些情况会触发离屏渲染,接下来就想办法尝试解决这些问题。
用父视图进行包裹
有的文章会提到用一个父视图包裹需要添加圆角的视图,然后将圆角添加到父视图上。
实践发现该方法并不能解决离屏渲染问题。
避免开启 masksToBounds
UIImageView
视图可以在不开启 masksToBounds
,仅设置 cornerRadius
的情况下显示圆角,只包含文字的 UIButton
对象也一样。
所以在这种情况下,不开启 masksToBounds
也可以避免触发离屏渲染。
为图片添加圆角
直接为图片添加圆角是比较常用的避免离屏渲染的方法。只不过现在比较常用的 UIImageView
视图已经不会触发离屏渲染了,但是 UIButton
依然可以这么做。
下面提几点实际需求里可能会遇到的问题:
“拼接图片”
例如微信的群聊头像,可能涉及到多个图片在一个视图控件中进行展示。
这个时候最好将多张图片拼到一起,然后对这张图片整体设置圆角。而不是在一个 UIView
中尝试添加多个 UIImageVIew
,再对 UIView
设置圆角。
视图尺寸不固定
一般 UIButton 会遇到 “尺寸不固定 + 需要圆角 + 背景色” 的情况。
例如页面某个位置有一个 “距离屏幕两侧 10px,可用状态背景色为蓝色,不可用状态下为灰蓝色” 的按钮。
此时可以考虑找 UI 切一个带圆角的纯色图片直接用作底色,但是我们开发也可以自己生成这样一张图片,然后通过拉伸来达到相同的效果。
首先我们先找喵神借用一个非常好用的枚举,来表示圆角:
extension UIImage {
enum Radius {
/// 圆角半径应该按照图片**宽度**的比例计算。
/// 通常关联的值应该在0和0.5之间,其中0表示没有圆角,0.5表示使用图片宽度的一半作为圆角半径。
case widthFraction(CGFloat)
/// 圆角半径应该按照图片**高度**的比例计算。
/// 通常关联的值应该在0和0.5之间,其中0表示没有圆角,0.5表示使用图片高度的一半作为圆角半径。
case heightFraction(CGFloat)
/// 使用一个固定的点值作为圆角半径。
case point(CGFloat)
func compute(with size: CGSize) -> CGFloat {
let cornerRadius: CGFloat
switch self {
case .point(let point):
cornerRadius = point
case .widthFraction(let widthFraction):
cornerRadius = size.width * widthFraction
case .heightFraction(let heightFraction):
cornerRadius = size.height * heightFraction
}
return cornerRadius
}
}
}
之后生成纯色图片:
extension UIImage {
static func color(
_ color: UIColor,
size: CGSize = .init(width: 1, height: 1),
radius: Radius? = nil
) -> UIImage {
let cornerRadius = radius?.compute(with: size)
let renderer = UIGraphicsImageRenderer(size: size)
let image = renderer.image { _ in
let rect = CGRect(origin: .zero, size: size)
let path: UIBezierPath
if let cornerRadius = cornerRadius {
path = .init(roundedRect: rect, cornerRadius: cornerRadius)
} else {
path = .init(rect: rect)
}
color.setFill()
path.fill()
}
guard let cornerRadius = cornerRadius else { return image }
let insets = { UIEdgeInsets(top: $0, left: $0, bottom: $0, right: $0) }(cornerRadius)
return image.resizableImage(withCapInsets: insets, resizingMode: .stretch)
}
}
你还可以扩展这个方法,配置需要圆角的位置,而不是为四个边都添加圆角,比较简单,这里就不再赘述了。