¿Cuál es la mejor manera de agregar una sombra paralela a mi UIView?

108

Estoy tratando de agregar una sombra paralela a las vistas que se superponen una encima de la otra, las vistas se colapsan permitiendo que se vea el contenido en otras vistas, en este sentido quiero mantener view.clipsToBoundsON para que cuando las vistas colapsen, su contenido se recorte.

Esto parece haberme dificultado agregar una sombra paralela a las capas, ya que cuando clipsToBoundsenciendo las sombras también se recortan.

He estado tratando de manipular view.framey view.boundspara agregar una sombra paralela al marco pero permitir que los límites sean lo suficientemente grandes como para abarcarlo, sin embargo, no he tenido suerte con esto.

Aquí está el código que estoy usando para agregar una sombra (esto solo funciona con clipsToBoundsAPAGADO como se muestra)

view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;

Aquí hay una captura de pantalla de la sombra que se aplica a la capa gris más clara superior. Con suerte, esto da una idea de cómo se superpondrá mi contenido si clipsToBoundsestá APAGADO.

Aplicación de sombra.

¿Cómo puedo agregar una sombra a UIViewmi contenido y mantenerlo recortado?

Editar: Solo quería agregar que también he jugado con el uso de imágenes de fondo con sombras, lo que funciona bien, sin embargo, aún me gustaría saber cuál es la mejor solución codificada para esto.

Wez
fuente

Respuestas:

280

Prueba esto:

UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;

En primer lugar : el UIBezierPathuso como shadowPathes crucial. Si no lo usa, es posible que no note una diferencia al principio, pero el ojo agudo observará un cierto retraso durante eventos como girar el dispositivo y / o similares. Es un cambio de rendimiento importante.

Con respecto a su problema específicamente : la línea importante es view.layer.masksToBounds = NO. Desactiva el recorte de las subcapas de la capa de la vista que se extienden más allá de los límites de la vista.

Para aquellos que se preguntan cuál es la diferencia entre masksToBounds(en la capa) y la clipToBoundspropiedad de la vista : realmente no hay ninguna. Alternar uno tendrá un efecto sobre el otro. Solo un nivel diferente de abstracción.


Swift 2.2:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.blackColor().CGColor
    layer.shadowOffset = CGSizeMake(0.0, 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.CGPath
}

Swift 3:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.cgPath
}
pkluz
fuente
1
Gracias, probé su código y luego intenté agregar masksToBounds = NO;a mi original, con ambos intentos, mantuve clipsToBounds = YES;ENCENDIDO, ambos no pudieron recortar el contenido. aquí hay una captura de pantalla de lo que sucedió con su ejemplo> youtu.be/tdpemc_Xdps
Wez
1
¿Alguna idea sobre cómo hacer que la sombra paralela abrace una esquina redondeada en lugar de renderizar como si la esquina todavía fuera un cuadrado?
John Erck
7
@JohnErck intente lo siguiente: UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. No probado, pero debería producir el resultado que desea.
pkluz
1
He aprendido que al animar la vista, la sombra deja huellas grises mientras se mueve. Eliminar las líneas shadowPath resolvió esto
ishahak
1
Se llama a @shoe porque en cualquier momento el tamaño de la vista cambia layoutSubviews(). Y si no compensamos en ese lugar ajustando el shadowPathpara reflejar el tamaño actual, tendríamos una vista redimensionada, pero la sombra seguiría siendo el tamaño anterior (inicial) y no se mostraría o asomarse donde no debería. .
pkluz
65

La respuesta de Wasabii en Swift 2.3:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.CGPath

Y en Swift 3/4/5:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.cgPath

Coloque este código en layoutSubviews () si está utilizando AutoLayout.

En SwiftUI, todo esto es mucho más fácil:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)
Bart van Kuik
fuente
11
Gracias por señalarlayoutSubviews
Islam Q.25 de
1
o incluso mejor en viewDidLayoutSubviews ()
Max MacLeod
1
prefiero los inicializadores de estructura rápidos sobre las variantes de Make, es decirCGSize(width: 0, height: 0.5)
Oliver Atkinson
Agregué este código a layoutSubviews (), todavía no se procesó en IB. ¿Hay otras configuraciones en las que deba participar?
burro
Gracias. Estaba usando Auto Layout y pensé para mí mismo: bueno ... view.boundsno se ha creado tan obviamente que shadowPathno pueda hacer referencia al rect de la vista, sino a algunos CGRectZero. De todos modos, ¡poner esto debajo layoutSubviewsresolvió mi problema por fin!
devdc
13

El truco es definir la masksToBoundspropiedad de la capa de su vista correctamente:

view.layer.masksToBounds = NO;

y debería funcionar.

( Fuente )

sergio
fuente
13

Puede crear una extensión para que UIView acceda a estos valores en el editor de diseño

Opciones de sombra en el editor de diseño

extension UIView{

    @IBInspectable var shadowOffset: CGSize{
        get{
            return self.layer.shadowOffset
        }
        set{
            self.layer.shadowOffset = newValue
        }
    }

    @IBInspectable var shadowColor: UIColor{
        get{
            return UIColor(cgColor: self.layer.shadowColor!)
        }
        set{
            self.layer.shadowColor = newValue.cgColor
        }
    }

    @IBInspectable var shadowRadius: CGFloat{
        get{
            return self.layer.shadowRadius
        }
        set{
            self.layer.shadowRadius = newValue
        }
    }

    @IBInspectable var shadowOpacity: Float{
        get{
            return self.layer.shadowOpacity
        }
        set{
            self.layer.shadowOpacity = newValue
        }
    }
}
Chris Stillwell
fuente
cómo hacer referencia a esa extensión en el generador de interfaces
Amr Angry
IBInspectable lo hace disponible en el creador de interfaces
Nitesh
¿Puedo lograr esto también en Objective C?
Harish Pathak
2
si desea tener una vista previa en tiempo real (en el generador de interfaces), aquí puede encontrar un buen artículo al respecto spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill
7

También puede configurar la sombra a su vista desde el guión gráfico

ingrese la descripción de la imagen aquí

pallavi
fuente
2

A la vistaWillLayoutSubviews:

override func viewWillLayoutSubviews() {
    sampleView.layer.masksToBounds =  false
    sampleView.layer.shadowColor = UIColor.darkGrayColor().CGColor;
    sampleView.layer.shadowOffset = CGSizeMake(2.0, 2.0)
    sampleView.layer.shadowOpacity = 1.0
}

Usando la extensión de UIView:

extension UIView {

    func addDropShadowToView(targetView:UIView? ){
        targetView!.layer.masksToBounds =  false
        targetView!.layer.shadowColor = UIColor.darkGrayColor().CGColor;
        targetView!.layer.shadowOffset = CGSizeMake(2.0, 2.0)
        targetView!.layer.shadowOpacity = 1.0
    }
}

Uso:

sampleView.addDropShadowToView(sampleView)
AG
fuente
1
Solo refuerza targetView: UIViewy quita el flequillo.
bluebamboo
6
¿Por qué no usar self? Me parece mucho más conveniente.
LinusGeffarth
3
Una mejor forma de hacer esto es eliminar targetView como parámetro y usar self en lugar de targetView. Esto elimina la redundancia y permite llamarlo así: sampleView.addDropShadowToView ()
maxcodes
Los comentarios de Max Nelson son geniales. Mi sugerencia es usar if letsintaxis en lugar de!
Johnny
0

Entonces, sí, debería preferir la propiedad shadowPath para el rendimiento, pero también: Desde el archivo de encabezado de CALayer.shadowPath

Especificar la ruta explícitamente usando esta propiedad generalmente * mejorará el rendimiento de la representación, al igual que compartir la misma ruta * referencia en múltiples capas

Un truco menos conocido es compartir la misma referencia en varias capas. Por supuesto, tienen que usar la misma forma, pero esto es común con las celdas de vista de tabla / colección.

No sé por qué se vuelve más rápido si comparte instancias, supongo que almacena en caché la representación de la sombra y puede reutilizarla para otras instancias en la vista. Me pregunto si esto es incluso más rápido con

Centro comercial Rufus
fuente