Comment contrôler la propagation et le flou de l'ombre?

113

J'ai conçu des éléments d'interface utilisateur dans l'esquisse, et l'un d'eux a une ombre avec flou 1 et propagation 0. J'ai regardé la documentation pour la propriété de la couche de vues et la couche n'a rien de nommé propagation ou flou, ou quelque chose d'équivalent (le seul contrôle était simplement shadowOpacity) Comment contrôler des choses comme le flou et la propagation?

ÉDITER:

Voici mes paramètres dans Sketch: Et voici à quoi je veux que mon ombre ressemble:Paramètres d'ombre d'esquisse


Shadow voulait




Et voici à quoi cela ressemble pour le moment: Remarque, vous devez cliquer sur l'image pour voir réellement l'ombre.

Ombre actuelle

Mon code est le suivant:

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
}
Quantaliinuxite
la source
Les balises ios (la plateforme), design (l'utilisation du logiciel Sketch) et Core-Graphics (il est possible d'utiliser un UIBezierPath pour dessiner l'ombre, ce qui pourrait être pertinent) sont toutes pertinentes, je ne vois pas pourquoi elles devraient être retiré.
Quantaliinuxite
vous voulez de l'ombre pour cette vue blanche seulement, non?
O-mkar
5
On dirait que le framework Sketch et CoreAnimation ont des métriques différentes, car il est toujours différent sur iOS et dans Sketch avec les mêmes paramètres.
Timur Bernikovich
Ah. Les joies de travailler avec des concepteurs qui utilisent des outils qui ne ressemblent guère ou pas du tout au fonctionnement d'iOS. Si vous passez à quelque chose comme PaintCode au lieu de Sketch, cela ne fonctionnera pas seulement comme iOS fonctionne, mais cela vous donnera également le code dont vous avez besoin. :-)
Fogmeister
Que faire si vous avez défini à la fois le rayon et le flou dans l'esquisse?
Vladimir

Réponses:

258

Voici comment appliquer les 6 propriétés d'ombre de Sketch au calque d'un UIView avec une précision presque parfaite:

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

Disons que nous voulons représenter ce qui suit:

entrez la description de l'image ici

Vous pouvez facilement le faire via:

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

ou plus succinctement:

myView.layer.applySketchShadow(y: 0)

Exemple:

entrez la description de l'image ici

À gauche: capture d'écran UIView de l'iPhone 8; à droite: rectangle d'esquisse.

Remarque:

  • Lors de l'utilisation d'un non-zéro spread, il code en dur un chemin basé sur boundsle CALayer. Si les limites de la couche changent, vous voudrez appeler à applySketchShadow()nouveau la méthode.
Sensé
la source
Hm. Dans Sketch, si vous appliquez l'étalement, il «sort», là où commence le flou / dégradé de l'ombre. Ce qui signifie - il reste à la couleur de l'ombre constante, jusqu'à ce que le flou commence. Mais si nous déplaçons le shadowLayer, l'ombre ne commencerait qu'à partir de ce calque, non? (Disons que l'étalement est de 10, l'ombre ne commencerait qu'après un décalage de 10, alors que comme dans Sketch, le flou / dégradé commencerait après un décalage de 10). Donc, ils ne correspondent pas vraiment, non?
Stephan
1
@Senseful Je suis intéressé de savoir comment vous avez réalisé cette formule?!
Hashem Aboonajmi
2
Vous avez oublié de définir maskToBounds = false.
ScottyBlades
1
Comme mentionné ci-dessus par ScottyBlades, cela ne fonctionnera pas sans maskToBounds = false. Un exemple d'échec est l'application de cette ombre à un fichier UIButton.
José
5
Cela devrait-il être shadowRadius = blur / UIScreen.main.scalepar opposition au codage en dur blur / 2.0? Ou, y a-t-il une autre raison de diviser par 2? @matchifang Avez-vous compris cela?
JWK
59

Vous pouvez essayer ceci ... vous pouvez jouer avec les valeurs. Le shadowRadiusdicte la quantité de flou. shadowOffsetdicte où va l'ombre.

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

Exemple avec spread

Exemple avec spread

Pour créer une ombre de base

    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

Exemple d'ombre de base dans Swift 2.0

PRODUCTION

O-mkar
la source
2
Les informations sont fausses, le décalage n'est pas un contrôle de propagation, c'est un contrôle de x et y.
Quantaliinuxite
@Quantaliinuxite J'ai accidentellement échangé les commentaires J'ai mis à jour les réponses
O-mkar
Bien, et maintenant vient le cœur de ma question: comment contrôler la propagation?
Quantaliinuxite
@Quantaliinuxite Le code mis à jour change maintenant 2.1 en quantité de propagation dont vous avez besoin
O-mkar
Merci d'avoir répondu. Ici, je ne suis pas en mesure de comprendre shadowOpacity (pourquoi il est utilisé).
Sunita
11

Esquisser l'ombre à l'aide d'IBDesignable et d'IBInspectable dans Swift 4

CROQUIS ET XCODE CÔTÉ À CÔTÉ

Examen de l'ombre

CODE

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

PRODUCTION

SORTIE DE DÉMO

COMMENT L'UTILISER

DEMO

O-mkar
la source
Ne devriez-vous pas revenir layer.shadowRadius * 2pour shadowBlur getter
berkuqo
7

Ce code a très bien fonctionné pour moi:

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
la source
La valeur de flou dans Sketch ne correspond pas au rayon de l'ombre de Core Animation.
CIFilter
Ce qui a fini par fonctionner assez près pour nous, c'est de réduire de moitié la valeur de flou dans le rayon d'ombre de Sketch for Core Animation.
CIFilter
2

Pour ceux qui tentent d'appliquer une ombre à un chemin prédéfini (comme pour une vue circulaire, par exemple), voici ce que j'ai obtenu:

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

Je posterai quelques exemples plus tard, mais cela a fonctionné sur place pour les vues circulaires pour moi.

Zig
la source
Ce "presque" fonctionne mais la propagation est incorrecte lorsque vous utilisez une propagation de 1avec xet y 0 shapeLayer.applyShadow(color: .black, alpha: 1, x: 0, y: 0, blur: 0, spread: 1, path: path). Dans ce cas, l'ombre a un décalage alors qu'elle ne le devrait pas. Ici, le triangle devrait avoir une ombre de 1px dans toutes les directions ibb.co/YjwRb9B
janvier
1

Ma solution basée sur ce post répond: (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
la source
0

J'aime beaucoup la réponse publiée ici et les suggestions dans les commentaires. Voici comment j'ai modifié cette solution:

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

USAGE:

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

C'est peut-être un peu creusé dans l'histoire, mais peut-être que certains ont eu le même problème. J'ai utilisé un échantillon de code de la réponse acceptée. Cependant, les effets sont assez différents: - la valeur y doit être d'environ la moitié par rapport à la même valeur dans l'esquisse - j'ai essayé d'appliquer une ombre sur la barre de navigation et l'effet est terriblement différent - à peine visible en utilisant les mêmes valeurs que l'esquisse.

Il semble donc que la méthode ne reflète totalement pas les paramètres de l'esquisse. Des indices?

Piotr Gawłowski
la source
Ce n'est pas une réponse - les commentaires vont dans les commentaires!
Dave Y