0

How can I add my gradient layer as a sub-layer to my view with the same size, which I want to use in other classes?

I tried to do it with "self.bounds" but it didn't work. If I give it strict dimensions, for example: gradientLayer.frame = CGRect(x: 0, y: 0, width: 100, height: 100) - it will work, but I need the same size as my view.

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 // Dont work with 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()
        }
    }
}
HangarRash
  • 7,314
  • 5
  • 5
  • 32
Gucci
  • 47
  • 1
  • 7
  • You're initialing with a`.zero`frame so until autolayout has done its work your view will have no layout and therefore no useful frame/bounds you can work with. – flanker Apr 07 '23 at 19:51
  • @flanker Thank you for your reply! :) Yes, I understand what you mean. But I don't want to use CGRect for the frame, just auto layout. When I will make override init(frame: CGRect) and super.init(frame: frame), I will have the same result. – Gucci Apr 07 '23 at 20:11
  • @SPatel Yeap! Thank you! I forgot that we have layoutSubviews()... – Gucci Apr 07 '23 at 20:25

1 Answers1

3

You can use the "base" layer of a view as a CAGradientLayer like this:

// use the "base" layer as a gradient layer
lazy var gradientLayer: CAGradientLayer = self.layer as! CAGradientLayer
override class var layerClass: AnyClass {
    return CAGradientLayer.self
}

So your class becomes:

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
    }()
    
    // use the "base" layer as a gradient layer
    lazy var gradientLayer: CAGradientLayer = self.layer as! CAGradientLayer
    override class var layerClass: AnyClass {
        return CAGradientLayer.self
    }

    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]
    }
    
    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()
        }
    }
}

and the gradient will always fill the view frame. No need to add sublayer(s).


Edit

If you need the gradient layer to be a sublayer, you need to update its frame in layoutSubviews():

override func layoutSubviews() {
    super.layoutSubviews()
    gradientLayer.frame = bounds
}

So, make let gradientLayer = CAGradientLayer() a property of your view...

final class ShimmerLabel: UIView {
    
    private let gradientLayer: CAGradientLayer = CAGradientLayer()

    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() {
        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.width.height.equalToSuperview()
        }
        
        addSubview(frontShimmerTextLabel)
        frontShimmerTextLabel.snp.makeConstraints { make in
            make.width.height.equalToSuperview()
        }
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        // update gradient layer frame
        gradientLayer.frame = bounds
    }
}
DonMag
  • 69,424
  • 5
  • 50
  • 86
  • Thank you for your reply! :) I know that it will work if we will cast it as self.layer... But I need a gradient layer on top of my view and labels, to create a shimmer effect... Do you have any idea? – Gucci Apr 07 '23 at 20:04
  • @Gucci - ok... you need to implement `layoutSubviews()` -- see the **Edit** to my answer. – DonMag Apr 07 '23 at 20:14
  • OMG! Solved! This is completely my fault... I forgot that we have layoutSubviews()... Thank you very much!! :) – Gucci Apr 07 '23 at 20:24