Multiple Drop Shadows on Single View iOS
Asked Answered
T

2

13

I'm looking to add multiple drop shadows with different opacities to a view. The specs for the shadows are as follows:

  • Y-offset of 4 with blur radius of 1
  • Y-offset of 10 with blur radius of 10
  • Y-offset of 2 with blur radius of 4
  • Blur radius of 1, spread of 1 (no offsets, will probably have to be 4 different shadows)

I can get all this working just fine using CALayers. Here's the code I have working for that (please note that I haven't bothered to set shadowPath yet, and won't until I get the multiple shadows thing working):

layer.cornerRadius = 4
layer.masksToBounds = false
layer.shouldRasterize = true
let layer2 = CALayer(layer: layer), layer3 = CALayer(layer: layer), layer4 = CALayer(layer: layer)
layer.shadowOffset = CGSizeMake(0, 4)
layer.shadowRadius = 1
layer2.shadowOffset = CGSizeMake(0, 10)
layer2.shadowRadius = 10
layer2.shadowColor = UIColor.blackColor().CGColor
layer2.shouldRasterize = true //Evidently not copied during initialization from self.layer
layer3.shadowOffset = CGSizeMake(0, 2)
layer3.shadowRadius = 4
layer3.shouldRasterize = true
layer4.shadowOffset = CGSizeMake(0, 1)
layer4.shadowRadius = 1
layer4.shadowOpacity = 0.1
layer4.shouldRasterize = true
layer.addSublayer(layer2)
layer.addSublayer(layer3)
layer.addSublayer(layer4)

(While this code is in Swift, I trust that it looks familiar enough to most Cocoa/Objective-C developers for it to make sense. Just know that layer is equivalent to self.layer in this context.)

The problem, however, arises when I attempt to use different opacities for each shadow. The shadowOpacity property of layer ends up being applied to all of its sublayers. This is a problem, as I need all of them to have their own shadow opacity. I have tried setting each layer's shadow opacity to its correct value (0.04, 0.12, etc.), but then the opacity of 0.04 of layer is applied to all sublayers. So I tried to set layer.shadowOpacity to 1.0, but this made all the shadows solid black. I also tried to be clever and do layer2.shadowColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0.12).CGColor, but it was just changed to full black with no transparency.

I suppose it makes some sort of sense that the layers should all have the same shadow opacity. But what's a way to get this working, varying opacities and all (doesn't have to utilize CALayer if it's easier another way)?

Please don't answer with "just use an image": no matter how sane that may be, I'm trying to avoid it. Just humor me.

Thanks.

EDIT: As per request, here's what I'm after: Goal.

Tripping answered 12/8, 2014 at 3:39 Comment(10)
can you post a screen shot of your view so I can have an idea of what are you trying to do and I might can help youSlosberg
@Slosberg Added an image of what I'm trying to achieve.Tripping
OK let me see what can we doSlosberg
Mmmmm I'm not familiar with Swift but let layer2 = CALayer(layer: layer) that line means layer2 is equal to layer? if so that's the problem you should initialize completely new instances of CALayer if not if you change a property in layer it will affect the other layersSlosberg
@Slosberg No, that's equivalent to [[CALayer alloc] initWithLayer: layer], which, according to Apple's docs, returns "A layer instance with any custom instance variables copied from layer" -- the properties are copied, it seems.Tripping
Ya OK try this after initialize the layers do: layer.addSublayer(layer2) layer.addSublayer(layer3) layer.addSublayer(layer4) Then play with the properties and see if there's still dependency between each otherSlosberg
@Slosberg I already have done that; the offsets get set independently of each other and all is well. The only issue is that the opacities, no matter what I do, are overridden by the superlayer's.Tripping
have you tried adding the layers in reverse order? add layer4 first then 3 then 2. shouldRasterize is set to true so it should work as you want I don't know if this work for what you want to achieve but have you tried CAGradientLayer?Slosberg
Did you ever get this to work?Carthusian
@Carthusian Nope, unfortunately: the project was more or less dropped. Do share if you figure it out, though!Tripping
N
4

The key thing that needs to be added is setting the layers' shadowPath. By default, Core Graphics draws a shadow around the layer's visible content, but in your code neither backgroundColor nor bounds are set for the layers, so the layers are actually empty.

Assuming you have a UIView subclass, you can make it work by adding something like this:

override func layoutSubviews() {
    super.layoutSubviews()
    layer.sublayers?.forEach { (sublayer) in
        sublayer.shadowPath = UIBezierPath(rect: bounds).cgPath
    }
}

I tested this approach on a view with multiple shadows and it worked as expected, as soon as the shadowPath is defined for the shadow layers. Different shadow colors and opacities worked as well, but you have to keep in mind that upper layers in the hierarchy will overlap the layers behind them, so if the front layer has a thick shadow, the other shadows can get hidden by it.

Nonagon answered 8/2, 2018 at 8:52 Comment(0)
O
-1

What about adding the alpha to the shadow color instead of the layer shadow opacity?

i.e. instead of

layer.shadowColor = UIColor.black.cgColor
layer.shadowOpacity = 0.5

do

layer.shadowColor = UIColor.black.withAlphaComponent(0.5).cgColor

for each layer.

Otoscope answered 4/7, 2018 at 0:19 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.