28

Estoy tratando de dibujar una sombra y un radio de esquina en una imagen. Puedo agregarlos por separado, pero no tengo manera de agregar ambos efectos al mismo tiempo. Estoy agregando una sombra con:No se puede agregar un radio de esquina y una sombra

[layer setShadowOffset:CGSizeMake(0, 3)]; 
[layer setShadowOpacity:0.4]; 
[layer setShadowRadius:3.0f]; 
[layer setShouldRasterize:YES]; 

Aquí, la capa es una CALayer de una subclase UIView. Por lo que este funciona cada vez que me puse

[layer setMasksToBounds:NO]; 

Ahora añadir un radio de esquina hago esto:

[layer setCornerRadius:7.0f]; 

pero necesito para establecer MasksToBounds a SÍ para que esto funcione:

[layer setMasksToBounds:YES]; 

¿De todos modos puedo agregar estos dos efectos?

Gracias por su tiempo,

Denis

Respuesta

57

Sí, sí hay ...

Si desea que un radio de esquina y una gota de sombra, no se presenta en - masksToBounds, sino más bien establecer el radio de la esquina y establecer el camino de bezier de la sombra con un rect redondeado. Mantener el radio de los dos el mismo:

[layer setShadowOffset:CGSizeMake(0, 3)]; 
[layer setShadowOpacity:0.4]; 
[layer setShadowRadius:3.0f]; 
[layer setShouldRasterize:YES]; 

[layer setCornerRadius:12.0f]; 
[layer setShadowPath: 
        [[UIBezierPath bezierPathWithRoundedRect:[self bounds] 
               cornerRadius:12.0f] CGPath]]; 

Es posible que desee comprobar su rendimiento sin el parámetro -shouldRasterize establecer una vez que se está configurando la ruta de sombra. El rendimiento del dibujo tiende a ser muy bueno una vez que ha establecido una ruta de sombra.

ACTUALIZACIÓN

no había mirado a este problema en un cierto tiempo, pero parece que ya no es necesario fijar un shadowPath con el fin de conseguir que esto funcione. Simplemente configurando cornerRadius y shadowOpacity funcionará ahora. Creo que este ha sido el caso desde iOS5 (por lo que puedo ver). Proporcionar esta actualización probablemente sea innecesario, ya que la configuración de esos parámetros 'simplemente funciona', pero lo proporcionaré para la posteridad. En resumen, esto es ahora todo lo que necesita:

[layer setShadowOpacity:0.4]; 
[layer setCornerRadius:12.0f]; 

Si aún necesita un mejor rendimiento, puede seguir adelante y establecer el parámetro shouldRasterize así:

[layer setShouldRasterize:YES]; 

Y hablando de rendimiento, que vale la pena teniendo en cuenta que si está notando animaciones lentas, querrá usar la técnica de establecer la ruta de sombra después de todo. Esta actualización fue solo para señalar que ya no es necesario establecer la ruta para lograr el efecto de mostrar tanto un radio de esquina como una sombra al mismo tiempo. Sin embargo, si el rendimiento es su prioridad, use una ruta.

ACTUALIZACIÓN 2

Dado que las personas parecen estar teniendo problemas para conseguir que esto funcione en algunos casos, voy a publicar un fragmento de código más completa aquí de un proyecto de ejemplo que he creado:

- (void)viewDidLoad 
{ 
    [super viewDidLoad]; 

    CALayer *layer = [CALayer layer]; 
    [layer setBounds:CGRectMake(0.0f, 0.0f, 100.0f, 200.0f)]; 
    [layer setPosition:[[self view] center]]; 
    [layer setBackgroundColor:[[UIColor lightGrayColor] CGColor]]; 
    [layer setShadowOpacity:0.55f]; 
    [layer setCornerRadius:8.0f]; 
    [layer setBorderWidth:1.0f]; 

    [[[self view] layer] addSublayer:layer]; 

    [[[self testView] layer] setShadowOpacity:0.55f]; 
    [[[self testView] layer] setShadowRadius:15.0f]; 
    [[[self testView] layer] setCornerRadius:8.0f]; 
    [[[self testView] layer] setBorderWidth:1.0f]; 
} 

El testView es un UIView que agregué en Interface Builder y configuré una salida. Esto es para asegurarse de que funciona de la misma manera en las dos capas que agrega explícitamente, así como en las capas dentro de las subvistas.

He probado esto en los simuladores para iOS5 a iOS6.1. Se da este resultado para mí en cada uno de ellos:

enter image description here

+1

¿Hay alguna razón para no establecer shouldRasterize a SÍ? – memmons

+2

Depende de la frecuencia con la que esté actualizando la capa. Si tiene la intención de configurarlo una vez y no actualizarlo nuevamente, rastrillarlo ayudará al rendimiento si está animando la capa. Sin embargo, si el contenido de la capa se actualiza con frecuencia, generará ese contenido como una imagen cada vez que cambie el contenido, lo que en realidad podría dañar su rendimiento de desplazamiento (suponiendo que esté desplazándose/animando). –

+0

Necesita espacio entre UIBezierPath y bezierPathWithRoundedRect. –

-2
view.layer.cornerRadius=4; 
[view.layer setMasksToBounds:YES]; 
[view.layer setShadowColor:SHADOW_COLOR]; 
[view.layer setShadowOpacity:4 ]; 
[view.layer setShadowRadius:4]; 
[view.layer setShadowOffset:0];  
[view.layer setShadowPath: [[UIBezierPath bezierPathWithRoundedRect:[view bounds] cornerRadius:CORNER_RADIUS] CGPath]]; 
view.layer.borderColor=[[UIColor lightGrayColor] colorWithAlphaComponent:.5].CGColor; 
view.layer.borderWidth=.4; 
+2

horrible. no trabajará. – Liam

3

El siguiente código Swift 3 muestra cómo dibujar una sombra y un radio de esquina en una imagen mediante el uso de CAShapeLayer y CALayer.

import UIKit 

class ViewController: UIViewController { 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     // constants 
     let radius: CGFloat = 20, offset = 8 
     let rect = CGRect(x: 0, y: 0, width: 200, height: 200) 

     // roundedView 
     let roundedView = UIView() 
     view.addSubview(roundedView) 

     // shadow layer 
     let shadowLayer = CALayer() 
     shadowLayer.shadowColor = UIColor.darkGray.cgColor 
     shadowLayer.shadowPath = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath 
     shadowLayer.shadowOffset = CGSize(width: offset, height: offset) 
     shadowLayer.shadowOpacity = 0.8 
     shadowLayer.shadowRadius = 2 
     roundedView.layer.addSublayer(shadowLayer) 

     // mask layer 
     let maskLayer = CAShapeLayer() 
     maskLayer.path = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath 

     // image layer 
     let imageLayer = CALayer() 
     imageLayer.mask = maskLayer 
     imageLayer.frame = rect 
     imageLayer.contentsGravity = kCAGravityResizeAspectFill 
     imageLayer.contents = UIImage(named: "image")?.cgImage 
     roundedView.layer.addSublayer(imageLayer) 

     // auto layout 
     roundedView.translatesAutoresizingMaskIntoConstraints = false 
     roundedView.widthAnchor.constraint(equalToConstant: rect.width).isActive = true 
     roundedView.heightAnchor.constraint(equalToConstant: rect.height).isActive = true 
     roundedView.centerXAnchor.constraint(equalTo: view.centerXAnchor).isActive = true 
     roundedView.centerYAnchor.constraint(equalTo: view.centerYAnchor).isActive = true 
    } 

} 

Este código genera la siguiente pantalla:

enter image description here


El código anterior puede ser reprogramado para utilizar los siguientes archivos rápidas:

CustomView.swift

import UIKit 

class CustomView: UIView { 

    var imageLayer: CALayer! 
    var image: UIImage? { 
     didSet { refreshImage() } 
    } 

    override var intrinsicContentSize: 


     CGSize { 
     return CGSize(width: 200, height: 200) 
    } 

    func refreshImage() { 
     if let imageLayer = imageLayer, let image = image { 
      imageLayer.contents = image.cgImage 
     } 
    } 

    override func layoutSubviews() { 
     super.layoutSubviews() 

     if imageLayer == nil { 
      let radius: CGFloat = 20, offset: CGFloat = 8 

      let shadowLayer = CALayer() 
      shadowLayer.shadowColor = UIColor.darkGray.cgColor 
      shadowLayer.shadowPath = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath 
      shadowLayer.shadowOffset = CGSize(width: offset, height: offset) 
      shadowLayer.shadowOpacity = 0.8 
      shadowLayer.shadowRadius = 2 
      layer.addSublayer(shadowLayer) 

      let maskLayer = CAShapeLayer() 
      maskLayer.path = UIBezierPath(roundedRect: bounds, cornerRadius: radius).cgPath 

      imageLayer = CALayer() 
      imageLayer.mask = maskLayer 
      imageLayer.frame = bounds 
      imageLayer.backgroundColor = UIColor.red.cgColor 
      imageLayer.contentsGravity = kCAGravityResizeAspectFill 
      layer.addSublayer(imageLayer) 
     } 


     refreshImage() 
    } 

} 

ViewController.swift

import UIKit 

class ViewController: UIViewController { 

    override func viewDidLoad() { 
     super.viewDidLoad() 

     let roundedView = CustomView() 
     roundedView.translatesAutoresizingMaskIntoConstraints = false 
     view.addSubview(roundedView) 

     // auto layout 
     let horizontalConstraint = roundedView.centerXAnchor.constraint(equalTo: view.centerXAnchor) 
     let verticalConstraint = roundedView.centerYAnchor.constraint(equalTo: view.centerYAnchor) 
     NSLayoutConstraint.activate([horizontalConstraint, verticalConstraint]) 

     roundedView.image = UIImage(named: "image") 
    } 

} 

Puede encontrar más formas de combinar las imágenes con las esquinas redondeadas y sombra en este Github repo.

2

Porque uso un UIButton con una imagen de fondo ninguna de estas respuestas funcionó para mí. Sigo sin sombra o sin bordes redondos en mis botones.

La forma más fácil en mi escenario era simplemente añadir otro punto de vista detrás del botón y añadir la sombra para que de este modo:

button.clipsToBounds=YES; 
button.layer.cornerRadius = 25; 

UIView *shadowView = [[UIView alloc]initWithFrame:button.frame]; 

shadowView.backgroundColor = [UIColor whiteColor];//needs this to cast shadow 
shadowView.layer.cornerRadius = 25; 
shadowView.clipsToBounds = YES; 
shadowView.layer.masksToBounds = NO; 
shadowView.layer.shadowOffset = CGSizeMake(0, 2); 
shadowView.layer.shadowRadius = 1; 
shadowView.layer.shadowOpacity = 0.2; 


[[button superview]addSubview:shadowView]; 
[[button superview]bringSubviewToFront:button]; 
Cuestiones relacionadas