Search code examples
iosswiftcalayershadowrounded-corners

UIButton with background image, rounded corners and a shadow


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


Solution

  • 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