自定义各圆角半径UIView遇优化警告,求更优实现方案
我明白你现在的处境:自己实现了可以单独设置每个圆角半径的UIView,功能跑起来没问题,但在视图调试模式下收到了苹果的优化警告,提示你改用cornerRadius+masksToBounds的方案。不过别担心——原生方案确实没法满足你“每个角独立设置不同半径”的需求,我们可以通过优化现有代码来消除警告,同时保持功能完整。
为什么会出现这个警告?
苹果的视图调试器之所以提示这个,是因为它认为用CAShapeLayer做简单的圆角遮罩(比如矩形圆角),性能不如系统原生的cornerRadius实现——系统对后者做了底层优化,比如减少离屏渲染的概率。但你的场景是四个角半径各不相同,原生的cornerRadius(包括配合maskedCorners的用法)只能给一组角设置同一个半径,完全没法覆盖你的需求,所以我们需要优化现有方案,而不是替换成原生方案。
核心优化:复用CAShapeLayer而非每次创建新实例
你的当前实现每次layoutSubviews都会新建一个CAShapeLayer作为遮罩,这不仅可能触发警告,还会带来不必要的对象创建开销。我们可以把CAShapeLayer作为类的属性提前初始化,后续只更新它的路径即可:
class CornerRadiusView: UIView { // 提前初始化遮罩图层,复用它 private let maskLayer = CAShapeLayer() private var topLeftRadius: CGFloat = 0 private var topRightRadius: CGFloat = 0 private var bottomLeftRadius: CGFloat = 0 private var bottomRightRadius: CGFloat = 0 override init(frame: CGRect) { super.init(frame: frame) setupMaskLayer() } required init?(coder: NSCoder) { super.init(coder: coder) setupMaskLayer() } private func setupMaskLayer() { layer.mask = maskLayer } func setCornerRadius(radius: CornerRadius) { topLeftRadius = radius.topLeft topRightRadius = radius.topRight bottomLeftRadius = radius.bottomLeft bottomRightRadius = radius.bottomRight setNeedsLayout() } override open func layoutSubviews() { super.layoutSubviews() updateMaskPath() } private func updateMaskPath() { let path = UIBezierPath( shouldRoundRect: bounds, topLeftRadius: topLeftRadius, topRightRadius: topRightRadius, bottomLeftRadius: bottomLeftRadius, bottomRightRadius: bottomRightRadius ) // 只更新路径,不用新建图层 maskLayer.path = path.cgPath } }
这样修改后,我们只创建一次CAShapeLayer,后续布局变化时仅更新它的路径,既减少了性能开销,也能让视图调试器更“满意”,大概率会消除那个警告。
可选优化:简化你的UIBezierPath实现
你的UIBezierPath扩展功能没问题,但代码可以更简洁易读。比如直接使用addArc(withCenter:radius:startAngle:endAngle:clockwise:)来绘制每个角的圆弧,逻辑会更清晰:
extension UIBezierPath { convenience init( roundedRect rect: CGRect, topLeftRadius: CGFloat, topRightRadius: CGFloat, bottomLeftRadius: CGFloat, bottomRightRadius: CGFloat ){ self.init() // 处理左上角圆角 let topLeftCenter = CGPoint(x: rect.minX + topLeftRadius, y: rect.minY + topLeftRadius) move(to: CGPoint(x: rect.minX + topLeftRadius, y: rect.minY)) addArc(withCenter: topLeftCenter, radius: topLeftRadius, startAngle: .pi * 3/2, endAngle: .pi, clockwise: false) // 处理右上角圆角 let topRightCenter = CGPoint(x: rect.maxX - topRightRadius, y: rect.minY + topRightRadius) addLine(to: CGPoint(x: rect.maxX - topRightRadius, y: rect.minY)) addArc(withCenter: topRightCenter, radius: topRightRadius, startAngle: .pi, endAngle: .pi/2, clockwise: false) // 处理右下角圆角 let bottomRightCenter = CGPoint(x: rect.maxX - bottomRightRadius, y: rect.maxY - bottomRightRadius) addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - bottomRightRadius)) addArc(withCenter: bottomRightCenter, radius: bottomRightRadius, startAngle: .pi/2, endAngle: 0, clockwise: false) // 处理左下角圆角 let bottomLeftCenter = CGPoint(x: rect.minX + bottomLeftRadius, y: rect.maxY - bottomLeftRadius) addLine(to: CGPoint(x: rect.minX + bottomLeftRadius, y: rect.maxY)) addArc(withCenter: bottomLeftCenter, radius: bottomLeftRadius, startAngle: 0, endAngle: .pi * 3/2, clockwise: false) close() } }
这个版本的代码逻辑更直观,每个角的绘制步骤一目了然,维护起来也更方便。
关于原生方案的补充说明
如果你的需求是“一组角用同一个半径”(比如左上角和右上角同半径,左下角和右下角同半径),那确实可以用原生的CALayer属性实现,完全不会有警告:
// 示例:左上角+右上角半径16,左下角+右下角半径8 layer.cornerRadius = 0 // 必须设为0,否则cornerRadii不生效 layer.cornerRadii = CGSize(width: 16, height: 8) layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMaxXMaxYCorner] layer.masksToBounds = true
但显然这不符合你要四个角独立设置的需求,所以复用CAShapeLayer的方案还是最适合你的。
内容的提问来源于stack exchange,提问作者Szymon W

