¿Por qué masksToBounds = YES previene la sombra CALayer?

83

Con el siguiente fragmento, agrego un efecto de sombra paralela a uno de mis UIView. Que funciona bastante bien. Pero tan pronto como establezco la propiedad masksToBounds de la vista en YES . El efecto de sombra paralela ya no se procesa.

self.myView.layer.shadowColor = [[UIColor blackColor] CGColor];
self.myView.layer.shadowOpacity = 1.0;
self.myView.layer.shadowRadius = 10.0;
self.myView.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
self.myView.layer.cornerRadius = 5.0;
self.myView.layer.masksToBounds = YES; // <-- This is causing the Drop shadow to not be rendered
UIBezierPath *path = [UIBezierPath bezierPathWithCurvedShadowForRect:self.myView.bounds];
self.myView.layer.shadowPath = path.CGPath;
self.myView.layer.shouldRasterize = YES;

¿Tiene alguna idea sobre esto?

jchatard
fuente

Respuestas:

166

Debido a que la sombra es un efecto que se realiza fuera de la Vista, y que masksToBounds establecido en YES le dirá a la UIView que no dibuje nada que esté fuera de sí mismo.

Si desea una vista de esquina redondeada con sombra, le sugiero que lo haga con 2 vistas:

UIView *view1 = [[UIView alloc] init];
UIView *view2 = [[UIView alloc] init];

view1.layer.cornerRadius = 5.0;
view1.layer.masksToBounds = YES;
view2.layer.cornerRadius = 5.0;
view2.layer.shadowColor = [[UIColor blackColor] CGColor];
view2.layer.shadowOpacity = 1.0;
view2.layer.shadowRadius = 10.0;
view2.layer.shadowOffset = CGSizeMake(0.0f, 0.0f);
[view2 addSubview:view1];
[view1 release];
El escuadrón
fuente
9
¡Maldito! ¡Eso tiene sentido!
jchatard
Muchas gracias ... Me ayudó mucho. Agradecido a usted .. :)
innodeasapps
1
Solo una nota, asegúrese de que la supervista [view2 en este caso] tenga un color de fondo claro, este era mi problema de todos modos
Adam Carter
@GangstaGraham asegúrese de agregar view2 como una subvista de view1.
pxpgraphics
¡Exactamente lo que necesitaba!
Rohan Sanap
17

Ahora es iOS 6, las cosas podrían haber cambiado. La respuesta de TheSquad no me funciona hasta que logré agregar una línea más view2.layer.masksToBounds = NO;, de lo contrario, la sombra no se muestra. Aunque la documentación dice masksToBoundsNO por defecto, mi código muestra lo contrario.

Así es como hago un botón de esquina redondeada con sombra, que se encuentra entre los fragmentos de código más utilizados en mi aplicación.

button.layer.masksToBounds = YES;
button.layer.cornerRadius = 10.0f;

view.layer.masksToBounds = NO;      // critical to add this line
view.layer.cornerRadius = 10.0f;
view.layer.shadowOpacity = 1.0f;
// set shadow path to prevent horrible performance
view.layer.shadowPath = 
    [UIBezierPath bezierPathWithRoundedRect:_button.bounds cornerRadius:10.0f].CGPath;      

[view addSubview:button];

EDITAR

Si las vistas necesitan ser animadas o desplazadas, masksToBounds = YESgrava el rendimiento significativamente, lo que significa que la animación probablemente se verá interrumpida. Para obtener esquinas redondeadas y sombras Y una animación o desplazamiento suave, use el siguiente código en su lugar:

button.backgroundColor = [UIColor clearColor];
button.layer.backgroundColor = [UIColor redColor].CGColor;
button.layer.masksToBounds = NO;
button.layer.cornerRadius = 10.0f;

view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = [UIBezierPath bezierPathWithRoundedRect:_button.bounds cornerRadius:10.0f].CGPath;
view.layer.shadowOffset = CGSizeMake(0.0f, 4.0f);
view.layer.shadowRadius = 2.0f;
view.layer.masksToBounds = NO;
view.layer.cornerRadius = 10.0f;  

[view addSubview:button];
Felipe007
fuente
4
+1 para la primera respuesta. Su respuesta actualizada no funcionará para las esquinas redondeadas. Si establece "masksToBounds" en "NO", las esquinas redondeadas desaparecerán. En lugar de cambiar eso, puede usar la propiedad "shouldRasterize" para obtener un buen rendimiento.
Dinesh Raja
Esto funciona ahora en 2020. Una solución tan simple solo para agregar una línea.
GeneCode
4

Esta es la versión Swift 3 e IBDesignable de la respuesta publicada por @TheSquad.

Usé el mismo concepto al realizar cambios en el archivo del guión gráfico. Primero moví mi targetView (el que requiere radio de esquina y sombra) dentro de un nuevo containerView . Luego agregué las siguientes líneas de código (Referencia: https://stackoverflow.com/a/35372901/419192 ) para agregar algunos atributos IBDesignable para la clase UIView:

@IBDesignable extension UIView {
/* The color of the shadow. Defaults to opaque black. Colors created
 * from patterns are currently NOT supported. Animatable. */
@IBInspectable var shadowColor: UIColor? {
    set {
        layer.shadowColor = newValue!.cgColor
    }
    get {
        if let color = layer.shadowColor {
            return UIColor(cgColor: color)
        }
        else {
            return nil
        }
    }
}

/* The opacity of the shadow. Defaults to 0. Specifying a value outside the
 * [0,1] range will give undefined results. Animatable. */
@IBInspectable var shadowOpacity: Float {
    set {
        layer.shadowOpacity = newValue
    }
    get {
        return layer.shadowOpacity
    }
}

/* The shadow offset. Defaults to (0, -3). Animatable. */
@IBInspectable var shadowOffset: CGPoint {
    set {
        layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
    }
    get {
        return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
    }
}

/* The blur radius used to create the shadow. Defaults to 3. Animatable. */
@IBInspectable var shadowRadius: CGFloat {
    set {
        layer.shadowRadius = newValue
    }
    get {
        return layer.shadowRadius
    }
}

/* The corner radius of the view. */
@IBInspectable var cornerRadius: CGFloat {
    set {
        layer.cornerRadius = newValue
    }
    get {
        return layer.cornerRadius
    }
}

Después de agregar este código, volví al guión gráfico y al seleccionar mi containerView ahora pude encontrar un nuevo conjunto de atributos en el inspector de atributos:

ingrese la descripción de la imagen aquí

Además de agregar valores para estos atributos según mi elección, también agregué un radio de esquina a mi targetView y establecí la propiedad masksToBounds como verdadera.

Espero que esto ayude :)

Kushal Ashok
fuente
4

Versión Swift 3.0 con StoryBoard

La misma idea con @TheSquad. Cree una nueva vista debajo de la vista actual y agregue sombra a la vista inferior.

1. Cree una vista debajo de la vista real.

Arrastre UIViewa StoryBoard con la misma restricción que su vista de destino. Marque el clip para enlazar con la vista de destino. También asegúrese de que la nueva vista esté listada antes de la vista de destino para que la vista de destino cubra la nueva vista.

ingrese la descripción de la imagen aquí

2. Ahora vincule la nueva vista a su código agregue agregar sombra en ella

Esto es solo una muestra. Puedes hacer lo que quieras aquí

shadowView.layer.masksToBounds = false
shadowView.layer.shadowColor = UIColor.red.cgColor
shadowView.layer.shadowOpacity = 0.5
shadowView.layer.shadowOffset = CGSize(width: -1, height: 1)
shadowView.layer.shadowRadius = 3

shadowView.layer.shadowPath = UIBezierPath(rect: coverImage.bounds).cgPath
shadowView.layer.shouldRasterize = true
Colmillos
fuente
2

También tuve problemas drásticos de rendimiento con sombras y esquinas redondeadas. En lugar de usar la parte shadowPath, usé las siguientes líneas que resolvieron perfectamente el impacto de rendimiento:

self.layer.shouldRasterize = YES;
self.layer.rasterizationScale = UIScreen.mainScreen.scale;
salocinx
fuente
Sin embargo, no se puede animar o retransmitir la pantalla con esto.
Van Du Tran
1

Esta es una de las soluciones:

     @IBOutlet private weak var blockView: UIView! {
         didSet {
          blockView.backgroundColor = UIColor.white
          blockView.layer.shadowColor = UIColor.black.cgColor
          blockView.layer.shadowOpacity = 0.5
          blockView.layer.shadowOffset = CGSize.zero

          blockView.layer.cornerRadius = 10
        }
      }
      @IBOutlet private weak var imageView: UIImageView! {
        didSet {
          imageView.layer.cornerRadius = 10
          imageView.layer.masksToBounds = true

          imageView.layer.shouldRasterize = true
        }
      }

ingrese la descripción de la imagen aquí

Svitlana
fuente