如何为UIView添加与视图同尺寸的CAGradientLayer子层并复用
问题描述
想要给自定义UIView添加一个尺寸和视图本身一致的CAGradientLayer子层,且该视图需要能在其他类中复用。尝试用self.bounds设置渐变层的frame时无效,但使用固定尺寸(比如CGRect(x: 0, y: 0, width: 100, height: 100))就能正常显示。附上ShimmerLabel类的代码示例:
final class ShimmerLabel: UIView { private lazy var backShimmerTextLabel: UILabel = { let label = UILabel() label.text = "Shimmer" label.textColor = UIColor(red: 65/255, green: 65/255, blue: 65/255, alpha: 1) label.font = UIFont.systemFont(ofSize: 50) label.textAlignment = .center return label }() private lazy var frontShimmerTextLabel: UILabel = { let label = UILabel() label.text = "Shimmer" label.textColor = .white label.font = UIFont.systemFont(ofSize: 50) label.textAlignment = .center return label }() init() { super.init(frame: .zero) configureGradientLayer() configureLayout() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func configureGradientLayer() { let gradientLayer = CAGradientLayer() gradientLayer.colors = [UIColor.white.cgColor, UIColor.black.cgColor] gradientLayer.locations = [0, 1] gradientLayer.frame = bounds // 这里用bounds不生效 :( layer.addSublayer(gradientLayer) } private func configureLayout() { addSubview(backShimmerTextLabel) backShimmerTextLabel.snp.makeConstraints { make in make.width.height.equalToSuperview() } addSubview(frontShimmerTextLabel) frontShimmerTextLabel.snp.makeConstraints { make in make.width.height.equalToSuperview() } } }
解决方案
问题原因
在init(frame: .zero)初始化时,当前ShimmerLabel的bounds还是CGRect.zero,此时给渐变层设置这个空的frame,后续视图布局完成后,渐变层的frame不会自动跟着更新,所以看不到效果。而固定尺寸能生效是因为直接给了明确的宽高。
修复步骤
- 将渐变层改为类属性:避免每次布局时重复添加子层,同时方便后续更新frame
- 在
layoutSubviews中更新渐变层frame:这个方法会在视图尺寸发生变化时被调用,此时视图的bounds已经是最终的布局尺寸,在这里设置渐变层的frame就能保证和视图尺寸一致
修改后的代码如下:
final class ShimmerLabel: UIView { private lazy var backShimmerTextLabel: UILabel = { let label = UILabel() label.text = "Shimmer" label.textColor = UIColor(red: 65/255, green: 65/255, blue: 65/255, alpha: 1) label.font = UIFont.systemFont(ofSize: 50) label.textAlignment = .center return label }() private lazy var frontShimmerTextLabel: UILabel = { let label = UILabel() label.text = "Shimmer" label.textColor = .white label.font = UIFont.systemFont(ofSize: 50) label.textAlignment = .center return label }() // 将渐变层改为类属性 private let gradientLayer = CAGradientLayer() init() { super.init(frame: .zero) configureGradientLayer() configureLayout() } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } private func configureGradientLayer() { gradientLayer.colors = [UIColor.white.cgColor, UIColor.black.cgColor] gradientLayer.locations = [0, 1] layer.addSublayer(gradientLayer) } private func configureLayout() { addSubview(backShimmerTextLabel) backShimmerTextLabel.snp.makeConstraints { make in make.edges.equalToSuperview() // 简化约束,让label完全铺满父视图 } addSubview(frontShimmerTextLabel) frontShimmerTextLabel.snp.makeConstraints { make in make.edges.equalToSuperview() } } // 重写方法更新渐变层frame override func layoutSubviews() { super.layoutSubviews() gradientLayer.frame = bounds } }
额外说明
- 把约束从
width.height.equalToSuperview()改成edges.equalToSuperview()更简洁,能确保label完全贴合父视图的边缘 - 如果需要支持从xib/storyboard初始化,需完善
init(coder:)方法,在其中调用configureGradientLayer()和configureLayout()
内容的提问来源于stack exchange,提问作者Gucci
相关产品推荐
相关产品推荐

