Good afternoon everyone. Tell me how to make two effects on one View.
I tried to do it through CALayer. But this method doesn't work!
// Create UIView.
let testView = UIView()
testView.backgroundColor = UIColor.gray
testView.layer.borderWidth = 1
testView.translatesAutoresizingMaskIntoConstraints = false
// Create layer with shadow.
let shadowUp = createShadowDown(buttonMenu)
// Add layer with shadow to UIView.
testView.layer.insertSublayer(layerOne, at: 0)
// Method create shadow.
func createShadowUp(_ size: UIView) -> CALayer {
let layer = CALayer()
layer.frame = size.bounds
layer.shadowColor = UIColor.red.cgColor
layer.shadowOpacity = 1
layer.shadowRadius = 10
layer.shadowOffset = CGSize(width: 0, height: 20)
return layer
}
The second method works, but you can't make two shadows! Here it most likely overwrites the values.
// Create UIView.
let testView = UIView()
testView.backgroundColor = UIColor.gray
testView.layer.borderWidth = 1
testView.translatesAutoresizingMaskIntoConstraints = false
// Create shadow.
createShadowTest(testView))
// Method create shadow.
func createShadowTest(_ view: UIView) {
view.layer.shadowColor = UIColor.red.cgColor
view.layer.shadowOpacity = 1
view.layer.shadowRadius = 10
view.layer.shadowOffset = CGSize(width: 0, height: 20)
}
Is it really not possible to add two shadows for one View?
I looked at the solutions on this site, but they don't work! For example here - Adding multiple shadows hides the background colour and text UIButton
// Create UIView.
let testView = UIView()
testView.backgroundColor = UIColor.gray
testView.layer.borderWidth = 1
testView.translatesAutoresizingMaskIntoConstraints = false
// Create shadow.
addDropShadow(testView))
// Method create shadow.
func addDropShadow() {
let topLayer = createShadowLayer(color: .red, offset: CGSize(width: 0, height: -20))
let bottomLayer = createShadowLayer(color: .green, offset: CGSize(width: 0, height: 20))
self.testView.layer.insertSublayer(topLayer, at: 0)
self.testView.layer.insertSublayer(bottomLayer, at: 1)
}
func createShadowLayer(color: UIColor, offset: CGSize) -> CALayer {
let shadowLayer = CALayer()
shadowLayer.masksToBounds = false
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = offset
shadowLayer.shadowRadius = 10
shadowLayer.shouldRasterize = true
shadowLayer.shadowPath = UIBezierPath(roundedRect: testView.bounds, cornerRadius: 10).cgPath
return shadowLayer
}
There are two problems here:
The UIView
was instantiated as UIView()
. That means that its frame.size
is .zero
.
A more subtle issue is that you set the shadowPath
using the then-current bounds
. But if the view frame is subsequently changed (whether manually or via layout constraints), the shadowPath
will not automatically update its path to reflect the new size.
I would suggest moving this shadowPath
adjustment logic into a UIView
subclass, so that as the view resizes, the respective shadowPath
values can be updated (in its layoutSubviews
method):
class ViewWithShadows: UIView {
private var shadowLayers: [CALayer] = []
override func layoutSubviews() {
super.layoutSubviews()
let path = shadowPath.cgPath
for shadowLayer in shadowLayers {
shadowLayer.shadowPath = path
}
}
func addShadowLayer(color: UIColor, offset: CGSize = .zero, radius: CGFloat = 10) {
let sublayer = shadowLayer(color: color, offset: offset, radius: radius)
layer.addSublayer(sublayer)
shadowLayers.append(sublayer)
}
func insertShadowLayer(color: UIColor, offset: CGSize = .zero, radius: CGFloat = 10, at index: UInt32) {
let sublayer = shadowLayer(color: color, offset: offset, radius: radius)
layer.insertSublayer(sublayer, at: index)
shadowLayers.append(sublayer)
}
}
private extension ViewWithShadows {
var shadowPath: UIBezierPath {
UIBezierPath(roundedRect: bounds, cornerRadius: 10)
}
// Method create shadow.
func shadowLayer(color: UIColor, offset: CGSize, radius: CGFloat) -> CALayer {
let shadowLayer = CALayer()
shadowLayer.masksToBounds = false
shadowLayer.shadowColor = color.cgColor
shadowLayer.shadowOpacity = 1
shadowLayer.shadowOffset = offset
shadowLayer.shadowRadius = radius
shadowLayer.shadowPath = shadowPath.cgPath
return shadowLayer
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addTwoShadowView()
}
func addTwoShadowView() {
// Create UIView.
let subview = ViewWithShadows()
subview.backgroundColor = .gray
subview.layer.borderWidth = 1
subview.translatesAutoresizingMaskIntoConstraints = false
view.addSubview(subview)
NSLayoutConstraint.activate([
subview.centerXAnchor.constraint(equalTo: view.centerXAnchor),
subview.centerYAnchor.constraint(equalTo: view.centerYAnchor),
subview.widthAnchor.constraint(equalToConstant: 100),
subview.heightAnchor.constraint(equalToConstant: 100)
])
subview.insertShadowLayer(color: .red, offset: CGSize(width: 0, height: -20), at: 0)
subview.insertShadowLayer(color: .green, offset: CGSize(width: 0, height: 20), at: 1)
}
}
Anyway, that yields:
There are lots of different ways to solve this, and this is just one. But hopefully it illustrates the idea. Just make sure that your shadow path updates as the view’s bounds
changes.
Personally, I would be inclined to not attempt to add different layers, and just have two views, each with a shadow applied to its main layer
, and eliminate the shadowPath
. That eliminates the logic associated with “adjust the shadowPath
when the bounds
change”, it will offer more graceful animation should the view’s bounds
change (should you need to do that), etc.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
addTwoShadowViews()
}
}
private extension ViewController {
func shadowView(color: UIColor, offset: CGSize, radius: CGFloat = 10) -> UIView {
let view = UIView()
view.translatesAutoresizingMaskIntoConstraints = false
view.backgroundColor = .gray
view.layer.masksToBounds = false
view.layer.shadowColor = color.cgColor
view.layer.shadowOpacity = 1
view.layer.shadowOffset = offset
view.layer.shadowRadius = radius
return view
}
func addTwoShadowViews() {
// Create UIView.
let redShadowView = shadowView(color: .red, offset: CGSize(width: 0, height: -20))
let greenShadowView = shadowView(color: .green, offset: CGSize(width: 0, height: 20))
view.addSubview(redShadowView)
view.addSubview(greenShadowView)
NSLayoutConstraint.activate([
redShadowView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
redShadowView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
redShadowView.widthAnchor.constraint(equalToConstant: 100),
redShadowView.heightAnchor.constraint(equalToConstant: 100),
greenShadowView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
greenShadowView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
greenShadowView.widthAnchor.constraint(equalToConstant: 100),
greenShadowView.heightAnchor.constraint(equalToConstant: 100)
])
}
}
That yields: