¿Cómo dibujo una sombra debajo de una UIView?

352

Estoy tratando de dibujar una sombra debajo del borde inferior de a UIViewen Cocoa Touch. Entiendo que debería usar CGContextSetShadow()para dibujar la sombra, pero la guía de programación 2D de Quartz es un poco vaga:

  1. Guarda el estado de los gráficos.
  2. Llame a la función CGContextSetShadow, pasando los valores apropiados.
  3. Realice todo el dibujo al que desea aplicar sombras.
  4. Restaurar el estado de los gráficos

He intentado lo siguiente en una UIViewsubclase:

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    CGContextRestoreGState(currentContext);
    [super drawRect: rect];
}

... pero esto no funciona para mí y estoy un poco atascado sobre (a) dónde ir después y (b) si hay algo que necesito hacer UIViewpara que esto funcione.

Fraser Speirs
fuente

Respuestas:

98

En su código actual, guarda el GStatecontexto actual, lo configura para dibujar una sombra ... y lo restaura a lo que era antes de configurarlo para dibujar una sombra. Luego, finalmente, invocas la implementación de la superclase de drawRect:.

Cualquier dibujo que deba verse afectado por la configuración de sombra debe realizarse después de

CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);

pero antes

CGContextRestoreGState(currentContext);

Entonces, si desea que las superclases drawRect:se 'envuelvan' en una sombra, ¿qué tal si reorganiza su código de esta manera?

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    [super drawRect: rect];
    CGContextRestoreGState(currentContext);
}
Christian Brunschen
fuente
789

Un enfoque mucho más fácil es establecer algunos atributos de capa de la vista en la inicialización:

self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

Necesita importar QuartzCore.

#import <QuartzCore/QuartzCore.h>
ollie
fuente
44
Pero tenga en cuenta que esto solo funciona en iOS 3.2+, por lo que si su aplicación debe funcionar en una versión anterior, debe usar la solución de Christian o una imagen estática detrás de la vista si es una opción.
florianbuerger
119
Esta solución también requiere agregar #import <QuartzCore/QuartzCore.h>"al archivo .h.
MusiGenesis
26
Establecer masksToBoundspara NOnegar el cornerRadius, ¿no?
pixelfreak
44
De acuerdo, para resolver eso, el color de fondo debe establecerse en la capa y la vista debe ser transparente.
pixelfreak
@pixelfreak ¿Cómo haces eso? Lo intenté self.layer.backgroundColor = [[UIColor whiteColor] CGColor];pero no tuve suerte. ¿Qué vista debe ser transparente?
Victor Van Hee
232
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

Esto ralentizará la aplicación. Agregar la siguiente línea puede mejorar el rendimiento siempre que su vista sea visiblemente rectangular:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
ZiOS
fuente
1
Probablemente valga la pena señalar que esta optimización solo es útil si su vista es visiblemente rectangular.
Benjamin Dobell
1
self.layer.shadowPath ... en lugar de qué? o simplemente agregando a él
Chrizzz
2
Simplemente agregue esa línea adicional además de las demás.
christophercotton
11
@NathanGaskin: dibujar sombras es una operación costosa, por ejemplo, si su aplicación permite otra orientación de interfaz y comienza a rotar el dispositivo, sin especificar explícitamente el camino de la sombra, debe representarse varias veces durante esa animación, lo que dependerá de la forma, probablemente ralentice visiblemente la animación
Peter Pajchl
99
@BenjaminDobell Esa línea en particular solo funciona para rectángulos, pero también puede crear trazados no rectangulares. Por ejemplo, si tiene un rectángulo redondeado, puede usar bezierPathWithRoundedRect: cornerRadius:
Dan Dyer
160

La misma solución, pero solo para recordarle: puede definir la sombra directamente en el guión gráfico.

Ex:

ingrese la descripción de la imagen aquí

Antzi
fuente
2
Lamentablemente, creo que CGColor está fuera del alcance del guión gráfico :(
Antzi
1
solo defina una categoría para UIViewo CGLayerque sea un UIColorcontenedor para la CGColorpropiedad;)
DeFrenZ
1
Este enlace describe cómo hacerlo: cocoadventures.org/post/104137872754/…
Kamchatka
8
Para que sea más fácil para las personas copiar y pegar: layer.masksToBound, layer.shadowOffset, layer.shadowRadius, layer.shadowOpacity. Los errores tipográficos te matarán aquí.
etayluz
3
El valor de layer.shadowOpacity debe ser 0.5 no 0,5
Desert Rose
43

Puedes probar esto ... puedes jugar con los valores. El shadowRadiusdicta la cantidad de desenfoque. shadowOffsetdicta dónde va la sombra.

Swift 2.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.CGPath

Swift 3.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height 
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height)) 
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.cgPath

Ejemplo con spread

Ejemplo con spread

Para crear una sombra básica

    demoView.layer.cornerRadius = 2
    demoView.layer.shadowColor = UIColor.blackColor().CGColor
    demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
    demoView.layer.shadowOpacity = 0.5 
    demoView.layer.shadowRadius = 5.0 //Here your control your blur

Ejemplo básico de sombra en Swift 2.0

SALIDA

O-mkar
fuente
19

Solución simple y limpia usando Interface Builder

Agregue un archivo llamado UIView.swift en su proyecto (o simplemente péguelo en cualquier archivo):

import UIKit

@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
        }
    }
}

Entonces esto estará disponible en Interface Builder para cada vista en el Panel de utilidades> Inspector de atributos:

Panel de utilidades

Puede configurar fácilmente la sombra ahora.

Notas:
- La sombra no aparecerá en IB, solo en tiempo de ejecución.
- Como dijo Mazen Kasser

Para aquellos que no lograron que esto funcione, [...] asegúrese de que Subvistas de Clip ( clipsToBounds) no esté habilitado

Axel Guilmin
fuente
Esta es la respuesta correcta: configuración sobre el código para futuras interfaces
Crake
Esta solución me lleva a un comportamiento que no funciona con el siguiente mensaje de advertencia (uno para cada propiedad):Failed to set (shadowColor) user defined inspected property on (UICollectionViewCell): [<UICollectionViewCell> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key shadowColor.
Xvolks
1
Tuve que importar UIKitpara que funcione, la Foundationimportación básica creada por XCode no es suficiente, pero se compila bien. Debería haber copiado todo el código fuente. Gracias Axel por esta buena solución.
Xvolks
13

Lo uso como parte de mis utilidades. Con esto no solo podemos establecer sombras, sino que también podemos obtener una esquina redondeada para cualquiera UIView. También puede establecer qué color de sombra prefiere. Normalmente se prefiere el negro, pero a veces, cuando el fondo no es blanco, es posible que desee algo más. Esto es lo que uso:

in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer 
              radius:(float)r 
              shadow:(BOOL)s
{
    [viewLayer setMasksToBounds:YES];
    [viewLayer setCornerRadius:r];        
    [viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
    [viewLayer setBorderWidth:1.0f];
    if(s)
    {
        [viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
        [viewLayer setShadowOffset:CGSizeMake(0, 0)];
        [viewLayer setShadowOpacity:1];
        [viewLayer setShadowRadius:2.0];
    }
    return;
}

Para usar esto, debemos llamar a esto: [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];

Srikar Appalaraju
fuente
7

Swift 3

extension UIView {
    func installShadow() {
        layer.cornerRadius = 2
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 1)
        layer.shadowOpacity = 0.45
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath
        layer.shadowRadius = 1.0
    }
}
neoneye
fuente
si utilizara este método, consideraría agregar parámetros para que pueda ajustar los valores de manera dinámica
Josh O'Connor
No me está dando una esquina redondeada, ¿estoy haciendo algo mal?
Nikhil Manapure
@NikhilManapure no sucede nada, esto podría deberse a que la función installShadow()no se invoca desde viewDidLoad()oviewWillAppear()
neoneye
Lo estaba llamando desde el drawmétodo de vista sobrecargado . La sombra viene correctamente pero la esquina redondeada no.
Nikhil Manapure
@NikhilManapure no tiene bordes redondeados, esto podría deberse a que la vista es una UIStackViewque solo tiene diseño y no tiene vista. Es posible que tenga que insertar una regular UIViewcomo contenedor para todo.
neoneye
6

Si desea utilizar StoryBoard y no desea seguir escribiendo atributos de tiempo de ejecución, puede crear fácilmente una extensión para las vistas y hacer que se puedan usar en el guión gráfico.

Paso 1. crear extensión

extension UIView {

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

@IBInspectable var shadowOpacity: Float {
    get {
        return layer.shadowOpacity
    }
    set {
        layer.shadowOpacity = newValue
    }
}

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

@IBInspectable var maskToBound: Bool {
    get {
        return layer.masksToBounds
    }
    set {
        layer.masksToBounds = newValue
    }
}
}

paso 2. ahora puedes usar estos atributos en el guión gráficoimagen del guión gráfico

king_T
fuente
3

Para aquellos que no pudieron hacer que esto funcione (¡como yo mismo!) Después de probar todas las respuestas aquí, solo asegúrese de que las Subvistas de Clip no estén habilitadas en el inspector de Atributos ...

Mazen Kasser
fuente
1

Swift 3

self.paddingView.layer.masksToBounds = false
self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
self.paddingView.layer.shadowRadius = 5
self.paddingView.layer.shadowOpacity = 0.5
Josh O'Connor
fuente
1

Puede usar mi función de utilidad creada para sombra y radio de esquina como se muestra a continuación:

- (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{

    // drop shadow
    [view.layer setShadowRadius:shadowRadius];
    [view.layer setShadowOpacity:shadowOpacity];
    [view.layer setShadowOffset:shadowOffset];
    [view.layer setShadowColor:shadowColor.CGColor];

    // border radius
    [view.layer setCornerRadius:cornerRadius];

    // border
    [view.layer setBorderColor:borderColor.CGColor];
    [view.layer setBorderWidth:borderWidth];
}

¡¡¡Espero que te ayude!!!

Sandip Patel - SM
fuente
1

Todos responden bien pero quiero agregar un punto más

Si encuentra un problema cuando tiene celdas de tabla, Deque una nueva celda no coincida en la sombra, por lo que en este caso, debe colocar su código de sombra en un método layoutSubviews para que se comporte bien en todas las condiciones.

-(void)layoutSubviews{
    [super layoutSubviews];

    [self.contentView setNeedsLayout];
    [self.contentView layoutIfNeeded];
    [VPShadow applyShadowView:self];
}

o en ViewControllers para una vista específica, coloque el código sombra dentro del siguiente método para que funcione bien

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

    [self.viewShadow layoutIfNeeded];
    [VPShadow applyShadowView:self.viewShadow];
}

He modificado mi implementación en la sombra para nuevos desarrolladores para una forma más generalizada, por ejemplo:

/*!
 @brief Add shadow to a view.

 @param layer CALayer of the view.

 */
+(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
    UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
    layer.masksToBounds = NO;
    layer.shadowColor = [UIColor blackColor].CGColor;
    layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
    layer.shadowOpacity = alpha;
    layer.shadowRadius = radius;// blur effect
    layer.shadowPath = shadowPath.CGPath;
}
Vishal16
fuente
1

Para los compañeros Xamarianos, la versión Xamarin.iOS / C # de la respuesta sería similar a la siguiente:

public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
{
    CGContext currentContext = UIGraphics.GetCurrentContext();
    currentContext.SaveState();
    currentContext.SetShadow(new CGSize(-15, 20), 5);
    base.DrawRect(area, formatter);
    currentContext.RestoreState();                
}

La principal diferencia es que adquiere una instancia CGContexten la que llama directamente a los métodos apropiados.

Martin Zikmund
fuente
1

Puedes usar esto Extensionpara agregar sombra

extension UIView {

    func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
    {
        layer.masksToBounds = false
        layer.shadowOffset = offset
        layer.shadowColor = color.cgColor
        layer.shadowRadius = radius
        layer.shadowOpacity = opacity

        let backgroundCGColor = backgroundColor?.cgColor
        backgroundColor = nil
        layer.backgroundColor =  backgroundCGColor
    }
}

puedes llamarlo como

your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)
paloma_39
fuente
0

Sketch Shadow usando IBDesignable e IBInspectable en Swift 4

CÓMO USARLO

MANIFESTACIÓN

DIBUJO Y XCODE LADO A LADO

Shadow Exampl

CÓDIGO

@IBDesignable class ShadowView: UIView {

    @IBInspectable var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }

    @IBInspectable var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set {
            layer.shadowOpacity = newValue
        }
    }

    @IBInspectable var shadowOffset: CGPoint {
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }

     }

    @IBInspectable var shadowBlur: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue / 2.0
        }
    }

    @IBInspectable var shadowSpread: CGFloat = 0 {
        didSet {
            if shadowSpread == 0 {
                layer.shadowPath = nil
            } else {
                let dx = -shadowSpread
                let rect = bounds.insetBy(dx: dx, dy: dx)
                layer.shadowPath = UIBezierPath(rect: rect).cgPath
            }
        }
    }
}

SALIDA

SALIDA DEMO

O-mkar
fuente
Puede mejorar el código eliminando los prefijos de sombra de los atributos. El objetivo de ShadowView es claro sobre la operación de sombra. Una cosa más es que puede agregar el radio de esquina a esta clase. (En la actualidad, la mayoría de las vistas de sombra implican el radio de la esquina)
mathema