iosswiftuikituilabelshadow

How to add multiple shadows to a label?


I want to add multiple shadows to a label. Red shadow and blue shadow. But my code doesn't work. I see only blue shadow.

private lazy var titleLabel: UILabel = {
    let label = UILabel()
    label.textColor = .black
    label.translatesAutoresizingMaskIntoConstraints = false
    return label
}()

override func viewDidLoad() {
    super.viewDidLoad()
    titleLabel.text = "1"
    addDropShadow(color: UIColor.red, offset: CGSize(width: -6, height: -6), btnLayer: titleLabel.layer)
    addDropShadow(color: UIColor.blue, offset: CGSize(width: 6, height: 6), btnLayer: titleLabel.layer)
}

private func addDropShadow(color: UIColor, offset: CGSize, btnLayer : CALayer) {
    btnLayer.masksToBounds = false
    btnLayer.shadowColor = color.cgColor
    btnLayer.shadowOpacity = 1
    btnLayer.shadowOffset = offset
    btnLayer.shadowRadius = 10
}

Add new code. But this case not work for me too. How to fix it? I can't see shadows at all

let layer1 = CALayer()
let  layer2 = CALayer()
        
titleLabel.text = "1"

layer1.shadowColor = UIColor.black.cgColor
layer1.shadowOpacity = 0.4
layer1.shadowOffset = CGSize.zero
layer2.shadowRadius = 4

layer2.shadowColor = UIColor.blue.cgColor
layer2.shadowOpacity = 0.4
layer2.shadowOffset = CGSize.zero    
layer2.shadowRadius = 4

layer3.shadowColor = UIColor.green.cgColor
layer3.shadowOpacity = 0.4
layer3.shadowOffset = CGSize.zero    
layer3.shadowRadius = 4
    
titleLabel.layer.insertSublayer(layer1, at: 1)
titleLabel.layer.insertSublayer(layer2, at: 2)
titleLabel.layer.insertSublayer(layer2, at: 3)

I want to add some shadows for the label like this result(2 shadows, red and blue):

enter image description here


Solution

  • One option is to create two labels, one with the red shadow and one with the blue shadow, and overlay them:

    enter image description here

    enter image description here

    Obviously, adjust the color, alpha, offset and radius to your liking.

    A second option could be to generate an image of the text with each color, apply a gaussian blur, and then layer them together.


    Edit - in response to comments...

    Your attempt with 3 sublayers doesn't do anything, because to get a shadow to show up on a layer, we need to either give that layer a .shadowPath (which we're not getting into here), or the layer must have something to "cast a shadow" (it doesn't really, but it's a good way to think about it).

    So, if we use this simple example:

    class ShadowTutorialVC: UIViewController {
        
        let theLabel = UILabel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            theLabel.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(theLabel)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                theLabel.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
                theLabel.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            ])
            
            if let testFont: UIFont = UIFont(name: "MarkerFelt-Thin", size: 40.0) {
                theLabel.font = testFont
            } else {
                theLabel.font = .systemFont(ofSize: 40.0, weight: .regular)
            }
            
            theLabel.text = "Type Something"
    
            theLabel.layer.shadowColor = UIColor.systemBlue.cgColor
            theLabel.layer.shadowOpacity = 1.0
            theLabel.layer.shadowOffset = .init(width: 12.0, height: 12.0)
            theLabel.layer.shadowRadius = 2.0
        }
        
    }
    

    we get this output:

    enter image description here

    If the .text of that label is "" (no text), it will be empty with NO shadow.

    Note that if we set the background color of the label (to very light gray):

    theLabel.backgroundColor = UIColor(white: 0.95, alpha: 1.0)
    

    we get this output:

    enter image description here

    That's because the label's .layer is now a solid color rectangle.

    Likewise, if you try to set the .shadow properties twice on the same layer, only the second settings will show up ... it's only a single layer with character glyphs on it.

    There are various ways to get your desired "double shadow" output... one method would be to get the character glyphs as a CGPath, then create two CALayers using that path as the .shadowPath.

    An easier approach - as I described here - is to overlay a second UILabel on top of the first, and give each label its own shadow properties.

    Pretty straightforward to create a UIView subclass to accomplish that "automatically".

    To make things easy on ourselves, we'll start with a "Shadow Definition" struct:

    struct ShadowDef {
        var color: UIColor = .black
        var opacity: Float = 1.0
        var offset: CGSize = .zero
        var radius: CGFloat = 2.0
    }
    

    Then we create a UIView subclass:

    class DoubleShadowLabel: UIView {
        
        // "typical" label properties... add any others you may want to implment
        public var text: String = "" { didSet { updateProperties() } }
        public var textColor: UIColor = .black { didSet { updateProperties() } }
        public var textAlignment: NSTextAlignment = .center { didSet { updateProperties() } }
        public var font: UIFont = .systemFont(ofSize: 17.0) { didSet { updateProperties() } }
        
        // shadow properties we can set from the controller
        public var shadow1: ShadowDef = ShadowDef() { didSet { updateProperties() } }
        public var shadow2: ShadowDef = ShadowDef() { didSet { updateProperties() } }
    
        // two labels... we will overlay label2 on top of label1
        private var label1: UILabel = UILabel()
        private var label2: UILabel = UILabel()
        
        override init(frame: CGRect) {
            super.init(frame: frame)
            commonInit()
        }
        required init?(coder: NSCoder) {
            super.init(coder: coder)
            commonInit()
        }
        private func commonInit() {
            // add and constrain the labels
            for v in [label1, label2] {
                v.translatesAutoresizingMaskIntoConstraints = false
                addSubview(v)
                NSLayoutConstraint.activate([
                    v.topAnchor.constraint(equalTo: topAnchor, constant: 0.0),
                    v.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 0.0),
                    v.trailingAnchor.constraint(equalTo: trailingAnchor, constant: 0.0),
                    v.bottomAnchor.constraint(equalTo: bottomAnchor, constant: 0.0),
                ])
            }
            updateProperties()
        }
        
        // this func will set the same "label" properties on both labels
        //  then the defined "shadow 1 & 2" properties on the respective labels
        private func updateProperties() {
            for v in [label1, label2] {
                v.text = text
                v.textColor = textColor
                v.textAlignment = textAlignment
                v.font = font
            }
    
            // set "under" label shadow properties
            label1.layer.shadowColor = shadow1.color.cgColor
            label1.layer.shadowOpacity = shadow1.opacity
            label1.layer.shadowOffset = shadow1.offset
            label1.layer.shadowRadius = shadow1.radius
    
            // set "over" label shadow properties
            label2.layer.shadowColor = shadow2.color.cgColor
            label2.layer.shadowOpacity = shadow2.opacity
            label2.layer.shadowOffset = shadow2.offset
            label2.layer.shadowRadius = shadow2.radius
        }
    }
    

    and, an example view controller to see it in action:

    class DoubleShadowTestVC: UIViewController {
        
        let testView = DoubleShadowLabel()
        
        override func viewDidLoad() {
            super.viewDidLoad()
            
            testView.translatesAutoresizingMaskIntoConstraints = false
            view.addSubview(testView)
            
            let g = view.safeAreaLayoutGuide
            NSLayoutConstraint.activate([
                testView.topAnchor.constraint(equalTo: g.topAnchor, constant: 40.0),
                testView.centerXAnchor.constraint(equalTo: g.centerXAnchor),
            ])
            
            if let testFont: UIFont = UIFont(name: "MarkerFelt-Thin", size: 40.0) {
                testView.font = testFont
            } else {
                testView.font = .systemFont(ofSize: 40.0, weight: .regular)
            }
            
            testView.text = "Type Something"
            
            testView.shadow1 = ShadowDef(color: .systemRed, opacity: 1.0, offset: .init(width: -1.0, height: -1.0), radius: 2.0)
            testView.shadow2 = ShadowDef(color: .systemBlue, opacity: 1.0, offset: .init(width: 1.0, height: 1.0), radius: 2.0)
        }
    
    }
    

    and we get this output:

    enter image description here

    And it becomes easy to "fine-tune" the shadows with these two lines (I'm exaggerating the offsets for effect):

    testView.shadow1 = ShadowDef(color: .systemGreen, opacity: 1.0, offset: .init(width: -10.0, height: -10.0), radius: 3.0)
    testView.shadow2 = ShadowDef(color: .systemYellow, opacity: 1.0, offset: .init(width: 10.0, height: 10.0), radius: 3.0)
    

    Result:

    enter image description here