自定义绘制UIButton如何适配高亮、禁用等不同状态的渲染需求
方案1:多CALayer实现(兼容iOS14+,优先推荐)
- 无需依赖按钮原生layer属性,新增2个自定义CALayer即可实现外边框、透明间隔、中心彩色的三层结构:
- 外边框layer:cornerRadius设为按钮宽度/2,fillColor设为clear,strokeColor设为tintColor,lineWidth为预期的外边框粗细,frame与按钮bounds完全对齐
- 中心彩色layer:边长为
按钮宽度 - 2*外边框粗细 - 2*透明间隔宽度,cornerRadius为边长/2,fillColor设为自定义颜色,frame居中于按钮bounds,天然和外边框留出透明空隙
- 状态适配逻辑:重写
tintColorDidChange方法,以及isHighlighted、isEnabled、isSelected的属性观察者,状态变更时直接修改对应layer的颜色、透明度即可,系统自带过渡动画,性能优于重写draw方案。
方案2:SFSymbol手动拼接方案(兼容iOS14+)
- 不需要iOS15的符号层级渲染能力,手动拼接两层内容即可实现需求:
- 先使用
UIImage(systemName: "circle")获取系统圆形边框符号,设置为alwaysOriginal渲染模式,用当前tintColor着色,得到外边框图片 - 开启图形上下文,先绘制外边框图片,再在中心位置绘制直径更小的自定义颜色圆形,导出为新的合成图片,设为按钮不同状态的image即可
- 先使用
方案3:重写draw方法适配状态实现
核心是监听所有状态变更时机触发重绘,示例代码如下:
class CustomColorWellButton: UIButton { // 自定义配置 var centerColor: UIColor = .systemBlue { didSet { setNeedsDisplay() } } private let borderWidth: CGFloat = 2 private let gapWidth: CGFloat = 2 // 监听所有状态变更,触发重绘 override var isHighlighted: Bool { didSet { setNeedsDisplay() } } override var isEnabled: Bool { didSet { setNeedsDisplay() } } override var isSelected: Bool { didSet { setNeedsDisplay() } } override func tintColorDidChange() { super.tintColorDidChange() setNeedsDisplay() } override func draw(_ rect: CGRect) { super.draw(rect) guard let context = UIGraphicsGetCurrentContext() else { return } let center = CGPoint(x: bounds.midX, y: bounds.midY) let outerRadius = bounds.width / 2 - borderWidth / 2 // 绘制外边框 context.setStrokeColor(tintColor.cgColor) context.setLineWidth(borderWidth) context.addArc(center: center, radius: outerRadius, startAngle: 0, endAngle: .pi * 2, clockwise: false) context.strokePath() // 根据状态调整中心色透明度 var finalCenterColor = centerColor if !isEnabled { finalCenterColor = finalCenterColor.withAlphaComponent(0.3) } else if isHighlighted { finalCenterColor = finalCenterColor.withAlphaComponent(0.7) } // 绘制中心彩色圆 let centerRadius = outerRadius - borderWidth / 2 - gapWidth context.setFillColor(finalCenterColor.cgColor) context.addArc(center: center, radius: centerRadius, startAngle: 0, endAngle: .pi * 2, clockwise: false) context.fillPath() } }
内容的提问来源于stack exchange,提问作者Runt8
相关产品推荐
相关产品推荐

