UIButton设置渐变背景后标题消失的原因及非图层方案解决方法
我遇到过类似的问题,先帮你分析一下原因,再给出符合要求的解决方案:
问题重现
你实现了自定义的GradientButton,用UIColor扩展生成渐变颜色作为背景,这个渐变在UILabel上正常显示,但给按钮设置标题后,标题却不见了。你的核心代码如下:
自定义GradientButton
class GradientButton: UIButton { private var colors: [UIColor] = [.white] private var direction: GradientDirection = .leftToRight override init(frame: CGRect) { super.init(frame: frame) } required init?(coder aDecoder: NSCoder) { fatalError("init(coder:) has not been implemented") } init(colors: [UIColor], direction: GradientDirection) { self.init() self.colors = colors self.direction = direction } override func layoutSubviews() { self.backgroundColor = UIColor.gradientColorWith(bounds: self.bounds, colors: self.colors, direction: self.direction) } }
渐变颜色实现
enum GradientDirection { case topToBottom case leftToRight case bottomToTop case rightToLeft case leftTopToRightBottom case leftBottomToRightTop case rightTopToLeftBottom case RightBottomToLeftTop } extension UIColor { static func gradientColorWith(bounds: CGRect, colors: [UIColor], direction: GradientDirection) -> UIColor { var cgColorArray: [CGColor] = [] for color in colors { cgColorArray.append(color.cgColor) } UIGraphicsBeginImageContextWithOptions(bounds.size, true, 1) guard let context = UIGraphicsGetCurrentContext() else { return .white } context.saveGState(); let colorSpace = colors.last?.cgColor.colorSpace let gradient = CGGradient.init(colorsSpace: colorSpace, colors: cgColorArray as CFArray, locations: nil)! var startPoint = CGPoint.zero var endPoint = CGPoint.zero let width = bounds.size.width let height = bounds.size.height switch direction { case .topToBottom: startPoint = CGPoint.init(x: 0, y: 0) endPoint = CGPoint.init(x: 0, y: height) case .leftToRight: startPoint = CGPoint.init(x: 0, y: 0) endPoint = CGPoint.init(x: width, y: 0) case .bottomToTop: startPoint = CGPoint.init(x: 0, y: height) endPoint = CGPoint.init(x: 0, y: 0) case .rightToLeft: startPoint = CGPoint.init(x: width, y: 0) endPoint = CGPoint.init(x: 0, y: 0) case .leftTopToRightBottom: startPoint = CGPoint.init(x: 0, y: 0) endPoint = CGPoint.init(x: width, y: height) case .leftBottomToRightTop: startPoint = CGPoint.init(x: 0, y: height) endPoint = CGPoint.init(x: width, y: 0) case .rightTopToLeftBottom: startPoint = CGPoint.init(x: width, y: 0) endPoint = CGPoint.init(x: 0, y: height) case .RightBottomToLeftTop: startPoint = CGPoint.init(x: width, y: height) endPoint = CGPoint.init(x: 0, y: 0) } context.drawLinearGradient(gradient, start: startPoint, end: endPoint, options: .drawsBeforeStartLocation) let image = UIGraphicsGetImageFromCurrentImageContext()! context.restoreGState() UIGraphicsEndImageContext() return UIColor.init(patternImage: image) } }
创建按钮的代码
private lazy var gradientButton: GradientButton = { let gradientButton = GradientButton.init(colors: [UIColor.orange, UIColor.purple], direction: .leftToRight) gradientButton.setTitle("This is a Button", for: .normal) gradientButton.setTitleColor(.black, for: .normal) gradientButton.titleLabel?.font = UIFont.boldSystemFont(ofSize: 30) return gradientButton }()
原因分析
问题出在你设置背景的方式上:
你通过self.backgroundColor = ...把渐变图案颜色赋值给UIButton的背景色,但UIButton的内部结构和普通UIView不同——它的backgroundColor会作用于专门的backgroundView,同时承载标题的contentView会默认继承这个背景色。而titleLabel的isOpaque属性默认是true,系统会用contentView的渐变背景填充标题区域,导致文字被背景完全覆盖,看起来就像标题消失了。
而UILabel没有这个问题,是因为它的背景色直接作用于自身,文字绘制在背景之上,没有额外的层级干扰。
解决方案(仅颜色相关方式,不添加图层)
我们只需要把渐变颜色设置到按钮的layer.backgroundColor上,而不是backgroundColor。这样渐变背景会作为底层图层的颜色,不会影响contentView和titleLabel的背景,标题就能正常显示在渐变之上。
同时,还有两个优化点需要注意:
- 在
layoutSubviews中必须先调用super.layoutSubviews(),保证按钮内部的标题等控件先完成布局,再设置背景。 - 生成渐变图片时,把
UIGraphicsBeginImageContextWithOptions的opaque参数设为false,并使用屏幕缩放比例,避免生成不必要的不透明背景,同时保证Retina屏幕上的清晰度。
修改后的代码如下:
修改GradientButton的layoutSubviews方法
override func layoutSubviews() { super.layoutSubviews() // 先完成内部控件布局 self.layer.backgroundColor = UIColor.gradientColorWith(bounds: self.bounds, colors: self.colors, direction: self.direction).cgColor }
修改渐变颜色生成方法的上下文初始化代码
// 替换原来的UIGraphicsBeginImageContextWithOptions调用 UIGraphicsBeginImageContextWithOptions(bounds.size, false, UIScreen.main.scale)
这样修改后,按钮的渐变背景正常显示,标题也能清晰地出现在背景之上,完全符合你要求的“仅通过颜色相关方式解决,不添加图层”的条件。
内容的提问来源于stack exchange,提问作者无夜之星辰
相关产品推荐
相关产品推荐

