Pregunta

Estoy tratando de dibujar una sombra debajo del borde inferior de un UIView en Cocoa Touch. Entiendo que debo usar CGContextSetShadow () para dibujar la sombra, pero la guía de programación de Quartz 2D 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 todos los dibujos a los que desea aplicar sombras.
  4. Restaurar el estado de los gráficos

He intentado lo siguiente en una subclase UIView :

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

... pero esto no me funciona y estoy un poco atascado con respecto a (a) a dónde ir a continuación y (b) si hay algo que deba hacer a mi UIView para hacer este trabajo?

¿Fue útil?

Solución

En su código actual, guarde el GState del contexto actual, configúrelo para dibujar una sombra ... y restaúrelo a lo que era antes de configurarlo para dibujar una sombra. Luego, finalmente, invoca la implementación de drawRect de la superclase:.

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

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

pero antes de

CGContextRestoreGState(currentContext);

Entonces, si quieres que el drawRect: de la superclase esté 'envuelto' en una sombra, ¿qué tal si reorganizas tu 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);
}

Otros consejos

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;

Necesitas importar QuartzCore.

#import <QuartzCore/QuartzCore.h>
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;

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

Ej:

ingrese la descripción de la imagen aquí

Puedes probar esto ... puedes jugar con los valores. El shadowRadius determina la cantidad de desenfoque. shadowOffset determina 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 propagación

 Ejemplo con propagación

  

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 de Basic Shadow en Swift 2.0

 SALIDA

Solución simple y limpia utilizando Interface Builder

Agrega un archivo llamado UIView.swift en tu proyecto (o simplemente pégalo 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

Puedes 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 funcionara, [...] asegúrese de que las Subvistas de clips ( clipsToBounds ) no estén habilitadas

Uso esto como parte de mis utils. Con esto no solo podemos establecer la sombra sino que también podemos obtener una esquina redondeada para cualquier UIView . También puedes establecer qué color de sombra prefieres. 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 necesitamos llamar a esto - [utils roundedLayer: yourview.layer radio: 5.0f shadow: YES];

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

Si desea usar StoryBoard y no desea seguir escribiendo los 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áfico  imagen del guión gráfico

Para aquellos que no lograron que esto funcionara (¡como yo mismo!) después de intentar todas las respuestas aquí, solo asegúrese de que Subvistas de clips no esté habilitada en el inspector de atributos ...

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

Puede usar mi función de utilidad creada para la sombra y el radio de la esquina de la siguiente manera:

- (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 !!!

Todas las respuestas están bien, pero quiero agregar un punto más

Si encuentra un problema cuando tiene celdas de tabla, deque una nueva celda, hay una falta de coincidencia 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 código de sombra de lugar de vista específico 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 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;
}

Para los compañeros de Xamarians, la versión de Xamarin.iOS / C # de la respuesta se vería como 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 de CGContext en la que llama directamente a los métodos apropiados.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top