I am trying to make a UIButton with rounded corners that has 2 colored shadows. Why is the red (and at this point also the blue "shadow" layer covering the button? How to get the shadows below the button canvas). I thought it was helping to insert sublayers instead of just adding them.
I have made a playground illustrating the issue
import UIKit
import PlaygroundSupport
This is the button I'm trying to implement
class PrimaryButton: UIButton {
required init(text: String = "Test 1", hasShadow: Bool = true) {
super.init(frame: .zero)
setTitle(text, for: .normal)
backgroundColor = UIColor.blue
layer.cornerRadius = 48 / 2
layer.masksToBounds = false
if hasShadow {
insertShadow()
}
}
fileprivate func insertShadow() {
let layer2 = CALayer(layer: layer), layer3 = CALayer(layer: layer)
layer2.applySketchShadow(color: UIColor.red, alpha: 0.5, x: 0, y: 15, blur: 35, spread: -10)
layer3.applySketchShadow(color: UIColor.blue, alpha: 0.5, x: 0, y: 10, blur: 21, spread: -9)
layer.insertSublayer(layer2, at: 0)
layer.insertSublayer(layer3, at: 0)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func layoutSubviews() {
super.layoutSubviews()
layer.sublayers?.forEach { (sublayer) in
sublayer.shadowPath = UIBezierPath(rect: bounds).cgPath
}
}
}
This is an extension that helps adding the shadow from Sketch specification:
extension CALayer {
func applySketchShadow(
color: UIColor = .black,
alpha: Float = 0.5,
x: CGFloat = 0,
y: CGFloat = 2,
blur: CGFloat = 4,
spread: CGFloat = 0)
{
shadowColor = color.cgColor
shadowOpacity = alpha
shadowOffset = CGSize(width: x, height: y)
shadowRadius = blur / 2.0
if spread == 0 {
shadowPath = nil
} else {
let dx = -spread
let rect = bounds.insetBy(dx: dx, dy: dx)
shadowPath = UIBezierPath(rect: rect).cgPath
}
masksToBounds = false
}
}
class MyViewController : UIViewController {
override func loadView() {
let view = UIView()
view.backgroundColor = .white
let button = PrimaryButton()
button.frame = CGRect(x: 150, y: 200, width: 200, height: 48)
view.addSubview(button)
self.view = view
}
}
// Present the view controller in the Live View window
PlaygroundPage.current.liveView = MyViewController()
It seems legit to me. layer1
& layer2
are sublayers of the button layer.
You could add a third layer that will serve as a background. Here is an example based on your code:
class PrimaryButton: UIButton {
let layer1 = CALayer(), layer2 = CALayer(), layer3 = CALayer()
override func layoutSubviews() {
super.layoutSubviews()
layer1.backgroundColor = UIColor.blue.cgColor
layer1.cornerRadius = 48 / 2
[layer1, layer2, layer3].forEach {
$0.masksToBounds = false
$0.frame = layer.bounds
layer.insertSublayer($0, at: 0)
}
layer2.applySketchShadow(color: UIColor.red, alpha: 0.5, x: 0, y: 15, blur: 35, spread: -10)
layer3.applySketchShadow(color: UIColor.blue, alpha: 0.5, x: 0, y: 10, blur: 21, spread: -9)
}
}
Note that I put most of the code inside layoutSubviews
because most of your methods use the actual bounds of the button.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With