iOS Swift圆角绘制与离屏渲染优化
iOS里面使用圆角有可能造成离屏渲染,它需要开辟一个新的内存空间,做上下文切换(状态切换),并且渲染完成后还要进行拷贝操作,因此会造成一定的性能损耗,需要进行优化。
1 原理
https://blog.bombox.org/2020-07-14/ios-offscreen-render/
这篇文章讲了离屏渲染的原理,非常清晰易懂。
离屏渲染的检测方法:
打开最新的Xcode(14.2)运行App
选菜单:Debug -> View Debugging -> Rendering -> Color Offscreen-Rendered Yellow
打开这个选项,看到App里面View的绿色或者黄色就说明发生了离屏渲染。
简单总结产生圆角离屏渲染的条件就是两点:多图层和裁剪
1.1 多图层
当前View有一下几种情况之一就会使用到多图层:
- 设置了background
- layer添加了子layer
- layer设置了mask
- 添加了子View
1.2 圆角
总共有4种方法可以产生圆角:
- masksToBounds裁剪
- layer.mask裁剪
- layer绘制圆角
- layer绘制圆角补角
4种方法示例如下,灰色是背景色,红色是添加的layer颜色
masksToBounds裁剪
这种方法是最简单常用的,它只能绘制最简单的圆角,作用于单图层时不会产生离屏渲染
layer.cornerRadius = 20
layer.masksToBounds = true
layer.mask裁剪
这种方法能绘制更复杂的圆角遮罩,但是它直接产生离屏渲染
let maskPath = UIBezierPath(...)
let maskLayer = CAShapeLayer()
maskLayer.path = maskPath.cgPath
layer.mask = maskLayer
layer绘制圆角
这种方法是生成一个圆角layer,不会产生离屏渲染
let layer = CAShapeLayer()
layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
layer.path = UIBezierPath(roundedRect: layer.frame, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: radii, height: radii)).cgPath
layer.fillColor = UIColor.red.cgColor
layer绘制圆角补角
这种方法是生成圆角的四个补角的图形layer盖在最上层,不会产生离屏渲染,使用前提是圆角补角的下层或者说背景必须是纯色的
let w:CGFloat = 200
let h:CGFloat = 200
let a90 = Double.pi * 0.5 // 90度
let path = UIBezierPath()
path.move(to: CGPoint(x: 0, y: radii))
path.addArc(withCenter: CGPoint(x: radii, y: radii), radius: radii, startAngle: -a90 * 2, endAngle: -a90, clockwise: true)
path.addLine(to: .zero)
path.close()
path.move(to: CGPoint(x: w - radii, y: 0))
path.addArc(withCenter: CGPoint(x: w - radii, y: radii), radius: radii, startAngle: -a90, endAngle: 0, clockwise: true)
path.addLine(to: CGPoint(x: w, y: 0))
path.close()
path.move(to: CGPoint(x: w, y: h - radii))
path.addArc(withCenter: CGPoint(x: w - radii, y: h - radii), radius: radii, startAngle: 0, endAngle: a90, clockwise: true)
path.addLine(to: CGPoint(x: w, y: h))
path.close()
path.move(to: CGPoint(x: radii, y: h))
path.addArc(withCenter: CGPoint(x: radii, y: h - radii), radius: radii, startAngle: a90, endAngle: a90 * 2, clockwise: true)
path.addLine(to: CGPoint(x: 0, y: h))
path.close()
let layer = CAShapeLayer()
layer.frame = CGRect(x: 0, y: 0, width: 200, height: 200)
layer.path = path.cgPath
layer.fillColor = UIColor.red.cgColor
2 圆角场景和使用
对于不同场景,使用不同的圆角方案:
- 没有子layer或子View的矩形圆角,用masksToBounds;
- 非矩形圆角或者有子layer子View,用layer绘制圆角;
- 圆角layer无法盖住时(比如圆角渐变,CAGradientLayer不能直接做圆角),用layer绘制圆角补角覆盖;
- 如果背景不是纯色,并且用layer绘制圆角补角有问题,就只能用layer.mask裁剪
注意:使用layer的圆角在View的大小变化时需要重新绘制,可以在layoutSubviews或者layoutSublayers方法里做