Isuru
Isuru

Reputation: 31283

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

Upvotes: 2

Views: 2077

Answers (3)

Omer Faruk Ozturk
Omer Faruk Ozturk

Reputation: 1852

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

Upvotes: 1

Shashank Mishra
Shashank Mishra

Reputation: 1079

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.

Upvotes: 1

Alexander Aryutin
Alexander Aryutin

Reputation: 76

You can add view.layer.masksToBounds = false

This will disable clipping for sublayer

https://developer.apple.com/documentation/quartzcore/calayer/1410896-maskstobounds

Upvotes: 0

Related Questions