¿Cómo controlar la propagación y el desenfoque de las sombras?

112

He diseñado elementos de la interfaz de usuario en el boceto, y uno de ellos tiene una sombra con desenfoque 1 y propagación 0. Busqué en el documento la propiedad de la capa de vistas y la capa no tiene nada llamado propagación o desenfoque, ni nada equivalente (el único control era simplemente shadowOpacity) ¿Cómo se pueden controlar cosas como el desenfoque y la propagación?

EDITAR:

Aquí están mis configuraciones en Sketch: Y así es como quiero que se vea mi sombra:Configuración de sombras de boceto


Sombra quería




Y así es como se ve en este momento: Tenga en cuenta que debe hacer clic en la imagen para ver la sombra.

Sombra actual

Mi código es el siguiente:

func setupLayer(){
    view.layer.cornerRadius = 2
    view.layer.shadowColor = Colors.Shadow.CGColor
    view.layer.shadowOffset = CGSize(width: 0, height: 1)
    view.layer.shadowOpacity = 0.9
    view.layer.shadowRadius = 5
}
Quantaliinuxita
fuente
Las etiquetas ios (la plataforma), diseño (el uso del software Sketch) y Core-Graphics (es posible usar un UIBezierPath para dibujar la sombra, lo que podría ser relevante) son todas relevantes, no veo por qué deberían Ser eliminado.
Quantaliinuxita
quieres sombra solo para esa vista blanca, ¿verdad?
O-mkar
5
Parece que el marco de Sketch y CoreAnimation tienen métricas diferentes, porque siempre se ve diferente en iOS y en Sketch con los mismos parámetros.
Timur Bernikovich
¡Ah! El placer de trabajar con diseñadores que utilizan herramientas que se parecen poco o nada a la forma en que funciona iOS. Si se cambia a algo como PaintCode en lugar de Sketch, no solo funcionará como funciona iOS, sino que también le proporcionará el código que necesita. :-)
Fogmeister
¿Qué pasa si ha establecido tanto el radio como el desenfoque en el boceto?
Vladimir

Respuestas:

256

A continuación, se explica cómo aplicar las 6 propiedades de sombra de Sketch a una capa de UIView con una precisión casi perfecta:

extension CALayer {
  func applySketchShadow(
    color: UIColor = .black,
    alpha: Float = 0.5,
    x: CGFloat = 0,
    y: CGFloat = 2,
    blur: CGFloat = 4,
    spread: CGFloat = 0)
  {
    shadowColor = color.cgColor
    shadowOpacity = alpha
    shadowOffset = CGSize(width: x, height: y)
    shadowRadius = blur / 2.0
    if spread == 0 {
      shadowPath = nil
    } else {
      let dx = -spread
      let rect = bounds.insetBy(dx: dx, dy: dx)
      shadowPath = UIBezierPath(rect: rect).cgPath
    }
  }
}

Digamos que queremos representar lo siguiente:

ingrese la descripción de la imagen aquí

Puede hacer esto fácilmente a través de:

myView.layer.applySketchShadow(
  color: .black, 
  alpha: 0.5, 
  x: 0, 
  y: 0, 
  blur: 4, 
  spread: 0)

o más sucintamente:

myView.layer.applySketchShadow(y: 0)

Ejemplo:

ingrese la descripción de la imagen aquí

Izquierda: captura de pantalla del iPhone 8 UIView; derecha: Rectángulo de boceto.

Nota:

  • Cuando se usa un valor distinto de cero spread, codifica una ruta basada en el boundsde CALayer. Si los límites de la capa alguna vez cambian, querrá volver a llamar al applySketchShadow()método.
Sentido
fuente
Hm. En Sketch, si aplica propagación, se 'mueve hacia afuera', donde comienza el desenfoque / degradado de la sombra. Lo que significa que permanece en el color de la sombra constante, hasta que comienza el desenfoque. Pero si movemos la shadowLayer, la sombra solo comenzaría desde esa capa, ¿verdad? (Digamos que la extensión es 10, la sombra solo comenzaría después de un desplazamiento de 10, donde, como en Sketch, el desenfoque / degradado comenzaría después de un desplazamiento de 10). Entonces realmente no coinciden, ¿verdad?
Stephan
1
@Senseful, ¿me interesa saber cómo lograste esta fórmula?
Hashem Aboonajmi
2
Olvidaste configurar maskToBounds = false.
ScottyBlades
1
Como lo mencionó ScottyBlades anteriormente, esto no funcionará sin maskToBounds = false. Un ejemplo en el que fallará es al aplicar esta sombra a un UIButton.
José
5
¿Debería ser esto shadowRadius = blur / UIScreen.main.scaleen contraposición a la codificación fija blur / 2.0? ¿O hay otra razón para dividir por 2? @matchifang ¿Descubriste esto?
JWK
59

Puedes probar esto ... puedes jugar con los valores. El shadowRadiusdicta la cantidad de desenfoque. shadowOffsetdicta a 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 de sombra básica en Swift 2.0

SALIDA

O-mkar
fuente
2
La información es incorrecta, el desplazamiento no es un control de propagación, es un control de xey.
Quantaliinuxita
@Quantaliinuxite accidentalmente intercambié los comentarios He actualizado las respuestas
O-mkar
Bien, y ahora viene el núcleo de mi pregunta: ¿cómo controlo la propagación?
Quantaliinuxita
@Quantaliinuxite El código actualizado ahora cambia 2.1 a la cantidad de propagación que necesita
O-mkar
Gracias por la respuesta. Aquí no puedo entender shadowOpacity (por qué se usa).
Sunita
11

Sombra de boceto con IBDesignable e IBInspectable en Swift 4

BOCETO Y XCODE DE LADO A LADO

Ejemplo de sombra

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 DE DEMOSTRACIÓN

CÓMO USARLO

MANIFESTACIÓN

O-mkar
fuente
¿No deberías regresar layer.shadowRadius * 2para shadowBlur getter?
berkuqo
7

Este código funcionó muy bien para mí:

yourView.layer.shadowOpacity = 0.2 // opacity, 20%
yourView.layer.shadowColor = UIColor.black.cgColor
yourView.layer.shadowRadius = 2 // HALF of blur
yourView.layer.shadowOffset = CGSize(width: 0, height: 2) // Spread x, y
yourView.layer.masksToBounds = false
Cesare
fuente
El valor de desenfoque en Sketch no coincide con el radio de sombra de Core Animation.
CIFilter
Lo que terminó funcionando lo suficientemente cerca para nosotros fue reducir a la mitad el valor de desenfoque en el radio de sombra de Sketch for Core Animation.
CIFilter
2

Para aquellos que intentan aplicar una sombra a una ruta predefinida (como para una vista circular, por ejemplo), esto es lo que terminé con:

extension CALayer {
    func applyShadow(color: UIColor = .black,
                     alpha: Float = 0.5,
                     x: CGFloat = 0,
                     y: CGFloat = 2,
                     blur: CGFloat = 4,
                     spread: CGFloat = 0,
                     path: UIBezierPath? = nil) {
        shadowColor = color.cgColor
        shadowOpacity = alpha
        shadowRadius = blur / 2
        if let path = path {
            if spread == 0 {
                shadowOffset = CGSize(width: x, height: y)
            } else {
                let scaleX = (path.bounds.width + (spread * 2)) / path.bounds.width
                let scaleY = (path.bounds.height + (spread * 2)) / path.bounds.height

                path.apply(CGAffineTransform(translationX: x + -spread, y: y + -spread).scaledBy(x: scaleX, y: scaleY))
                shadowPath = path.cgPath
            }
        } else {
            shadowOffset = CGSize(width: x, height: y)
            if spread == 0 {
                shadowPath = nil
            } else {
                let dx = -spread
                let rect = bounds.insetBy(dx: dx, dy: dx)
                shadowPath = UIBezierPath(rect: rect).cgPath
            }
        }
        shouldRasterize = true
        rasterizationScale = UIScreen.main.scale
    }
}

Publicaré algunos ejemplos más adelante, pero esto me ha funcionado perfectamente para las vistas circulares.

Zig
fuente
Este "casi" funciona, pero la extensión es incorrecta cuando usa una extensión de 1con xy y 0 shapeLayer.applyShadow(color: .black, alpha: 1, x: 0, y: 0, blur: 0, spread: 1, path: path). En este caso, la sombra tiene un desplazamiento, mientras que no debería. Aquí el triángulo debe tener la 1px sombra se extendió en todas las direcciones ibb.co/YjwRb9B
Ene
1

Mi solución basada en esta publicación responde: (Swift 3)

let shadowPath = UIBezierPath(rect: CGRect(x: -1,
                                           y: -2,
                                           width: target.frame.width + 2,
                                           height: target.frame.height + 2))

target.layer.shadowColor = UIColor(hexString: shadowColor).cgColor
target.layer.shadowOffset = CGSize(width: CGFloat(shadowOffsetX), height: CGFloat(shadowOffsetY))
target.layer.masksToBounds = false
target.layer.shadowOpacity = Float(shadowOpacity)
target.layer.shadowPath = shadowPath.cgPath
Josep Escobar
fuente
0

Realmente me gusta la respuesta publicada aquí y las sugerencias en los comentarios. Así es como modifiqué esa solución:

extension UIView {
  func applyShadow(color: UIColor, alpha: Float, x: CGFloat, y: CGFloat, blur: CGFloat, spread: CGFloat) {
    layer.masksToBounds = false
    layer.shadowColor = color.cgColor
    layer.shadowOpacity = alpha
    layer.shadowOffset = CGSize(width: x, height: y)
    layer.shadowRadius = blur / UIScreen.main.scale
    if spread == 0 {
      layer.shadowPath = nil
    } else {
      let dx = -spread
      let rect = bounds.insetBy(dx: dx, dy: dx)
      layer.shadowPath = UIBezierPath(rect: rect).cgPath
    }
  }
}

USO:

myButton.applyShadow(color: .black, alpha: 0.2, x: 0, y: 1, blur: 2, spread: 0)
budidino
fuente
-1

Puede ser un poco de excavación en la historia, pero tal vez algunos tuvieron el mismo problema. Usé una muestra de código de la respuesta aceptada. Sin embargo, los efectos son bastante diferentes: - el valor y tiene que ser aproximadamente la mitad en comparación con el mismo valor en el boceto - Traté de aplicar sombra en la barra de navegación y el efecto es terriblemente diferente - apenas visible al usar los mismos valores que tenía el boceto.

Entonces parece que el método no refleja en absoluto los parámetros del boceto. ¿Alguna pista?

Piotr Gawłowski
fuente
Esta no es una respuesta, ¡los comentarios van en comentarios!
Dave Y