UIButton with background image, rounded corners and a shadow

Viewed 1485

I'm trying to create a UIButton with rounded corners, a background image and a shadow. Before adding the shadow, everything works fine.

enter image description here

But after adding shadow values, the shadow doesn't show up. Obviously due to clipsToBounds property value being set to true. If I remove that, it looks like this.

enter image description here

Since I need the corner radius as well, I cannot have the clipsToBounds be false.

This is my code.

class CustomButton: UIButton {
    
    var cornerRadius: CGFloat {
        get {
            return layer.cornerRadius
        }
        set {
            layer.cornerRadius = newValue
            clipsToBounds = true
        }
    }
    
    var shadowRadius: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue
        }
    }
    
    var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set {
            layer.shadowOpacity = newValue
        }
    }
    
    var shadowOffset: CGSize {
        get {
            return layer.shadowOffset
        }
        set {
            layer.shadowOffset = newValue
        }
    }
    
    var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }
    
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        
    }
    
}


private lazy var button: CustomButton = {
    let button = CustomButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setBackgroundImage(UIImage(named: "Rectangle"), for: .normal)
    button.setTitleColor(.white, for: .normal)
    button.setTitle("Sign Up", for: .normal)
    button.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
    button.cornerRadius = 20
    button.shadowColor = .systemGreen
    button.shadowRadius = 10
    button.shadowOpacity = 1
    button.shadowOffset = CGSize(width: 0, height: 0)
    return button
}()

Is there a workaround to have both the shadow and the corner radius?

Demo project

3 Answers

You need to use two separate views for shadow and image. I can't find any solution to set image, shadow, and corner radius using the same button layer.

Make button's corner radius(clipsToBounds=true) rounded and set the image on it.

Take a shadow view under the button with proper shadow radius and offset.

You can do it via adding shadow and background image with different layer.

First, if you don't need the properties, remove all and modify your CustomButton implementation just like below (modify as your need):

class CustomButton: UIButton {
    
    private let cornerRadius: CGFloat = 20
    private var imageLayer: CALayer!
    private var shadowLayer: CALayer!
    
    override func draw(_ rect: CGRect) {
        addShadowsLayers(rect)
    }
    
    private func addShadowsLayers(_ rect: CGRect) {
        // Add Image
        if self.imageLayer == nil {
            let imageLayer = CALayer()
            imageLayer.frame = rect
            imageLayer.contents = UIImage(named: "Rectangle")?.cgImage
            imageLayer.cornerRadius = cornerRadius
            imageLayer.masksToBounds = true
            layer.insertSublayer(imageLayer, at: 0)
            self.imageLayer = imageLayer
        }
        
        // Set the shadow
        if self.shadowLayer == nil {
            let shadowLayer = CALayer()
            shadowLayer.masksToBounds = false
            shadowLayer.shadowColor = UIColor.systemGreen.cgColor
            shadowLayer.shadowOffset = .zero
            shadowLayer.shadowOpacity = 1
            shadowLayer.shadowRadius = 10
            shadowLayer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).cgPath
            layer.insertSublayer(shadowLayer, at: 0)
            self.shadowLayer = shadowLayer
        }
    }
}

And initialize your button like below:

private lazy var button: CustomButton = {
    let button = CustomButton()
    button.translatesAutoresizingMaskIntoConstraints = false
    button.setTitleColor(.white, for: .normal)
    button.setTitle("Sign Up", for: .normal)
    button.titleLabel?.font = UIFont.systemFont(ofSize: 15, weight: .semibold)
    return button
}()

enter image description here

Related