如何控制阴影的扩散和模糊程度?

138

我在Sketch中设计了UI元素,其中一个有模糊值为1、扩展值为0的阴影。我查看了视图层属性的文档,发现层没有名为扩展或模糊的任何内容,也没有任何等效的东西(唯一的控制只是shadowOpacity)。如何控制像模糊和扩展这样的属性?

以下是我的Sketch设置:

Sketch shadow settings

这是我想要的阴影效果:

Shadow wanted

而目前的阴影效果如下:

Current shadow

请注意,您必须单击图片才能看到阴影。

我的代码如下:

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
}

标签iOS(平台)、设计(使用软件Sketch)和Core-Graphics(可以使用UIBezierPath绘制阴影,这可能是相关的)都很重要,我不明白为什么它们应该被删除。 - Quantaliinuxite
你想要那个白色视图的阴影,对吗? - O-mkar
8
看起来 Sketch 和 CoreAnimation 框架有不同的度量标准,因为在 iOS 上和在 Sketch 中使用相同参数时,它们始终看起来不同。 - Timur Bernikovich
啊,与那些使用与iOS工作方式几乎没有相似之处的工具的设计师一起工作真是令人愉悦。如果你转而使用像PaintCode这样的工具,它不仅会像iOS一样工作,还会为你提供所需的代码。 :-) - Fogmeister
如果在 Sketch 中同时设置了半径和模糊度,会发生什么? - Vladimir
9个回答

301

以下是如何将所有6个Sketch阴影属性应用于UIView的图层,并几乎完美地实现:

extension CALayer {
  func applySketchShadow(
    color: UIColor = .black,
    alpha: Float = 0.5,
    x: CGFloat = 0,
    y: CGFloat = 2,
    blur: CGFloat = 4,
    spread: CGFloat = 0)
  {
    masksToBounds = false
    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
    }
  }
}

假设我们想要表示以下内容:

enter image description here

你可以通过以下方式轻松实现:

myView.layer.applySketchShadow(
  color: .black, 
  alpha: 0.5, 
  x: 0, 
  y: 0, 
  blur: 4, 
  spread: 0)
更简洁地说:
myView.layer.applySketchShadow(y: 0)

例子:

enter image description here

左边:iPhone 8视图截屏;右边:Sketch矩形。

注意:

  • 当使用非零的 spread 时,它会基于 CALayer 的 bounds 固定一个路径。如果该层的边界发生变化,您需要再次调用 applySketchShadow() 方法。

2
@Senseful 我很想知道您是如何实现这个公式的?! - Hashem Aboonajmi
4
你忘记设置 maskToBounds = false - ScottyBlades
1
你怎么知道模糊度为4等同于阴影半径4/2.0?我想了解它们之间的关系。谢谢。 - matchifang
1
正如ScottyBlades所提到的,如果没有设置maskToBounds = false,这个方法是行不通的。一个失败的例子是将这个阴影应用于一个UIButton上。 - José
9
这句话的意思是:这个代码应该是 shadowRadius = blur / UIScreen.main.scale 而不是写死 blur / 2.0 吗?或者说,除以2是否有其他原因?@matchifang,你弄清楚了吗? - JWK
显示剩余6条评论

59

你可以尝试这个...你可以调整数值进行调试。

shadowRadius决定模糊程度。 shadowOffset决定阴影的位置。

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

(翻译: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

使用Spread示例

使用Spread示例

创建基本阴影

    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

Swift 2.0中基本的阴影实例

输出


2
信息是错误的,偏移量不是传播控制,而是x和y的控制。 - Quantaliinuxite
@Quantaliinuxite 我不小心交换了注释,我已经更新了答案。 - O-mkar
好的,现在来到我的问题的核心:我如何控制传播? - Quantaliinuxite
@Quantaliinuxite 更新了代码,现在将2.1更改为您需要的价差金额。 - O-mkar
谢谢回答。在这里我不理解shadowOpacity(为什么要使用它)。 - Sunita
显示剩余2条评论

15

使用Swift 4中的IBDesignable和IBInspectable绘制阴影

Sketch与Xcode并排显示

Shadow Exampl

代码

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

输出

演示输出

如何使用

演示


你应该为shadowBlur getter返回layer.shadowRadius * 2,对吗? - mamba4ever
1
你为什么要乘以或除以阴影半径?有人能解释一下吗? - midnight

8
这段代码对我非常有效:

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

Sketch中的模糊值与Core Animation的阴影半径不匹配 - CIFilter
最终对我们有效的方法是在Sketch中将模糊值减半,用于Core Animation的阴影半径。 - CIFilter
1
传递和偏移是不同的参数。不要让人们感到困惑! - Vyachaslav Gerchicov

3

对于那些试图将阴影应用到预定义路径(例如圆形视图)的人,这是我最终得出的结果:

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

我稍后会发布一些例子,但是对于圆形视图来说,这对我非常有效。

这个代码“几乎”可以工作,但是当你使用 xy 值为 0 以及 spread 值为 1 时,阴影的扩散是错误的。在这种情况下,阴影有一个偏移量,而它不应该有。在这里,三角形的阴影应该在所有方向上扩散1像素。https://ibb.co/YjwRb9B - Jan

1

根据这篇帖子的回复,我的解决方案是:(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

1

稍作修改,参考了@Senseful的答案,这个方案适用于我的项目。

  • 支持阴影圆角(适用于某些圆角视图)
  • spread == 0仍然应用阴影效果(看起来像边框)
struct SketchShadow {
    var color: UIColor = .black
    let alpha: Float = 0.1
    let x: CGFloat
    let y: CGFloat
    let blur: CGFloat
    let spread: CGFloat
    let cornorRadius: CGFloat
    
    func applyToLayer(_ layer: CALayer) {
        layer.masksToBounds = false
        layer.shadowColor = color.cgColor
        layer.shadowOpacity = alpha
        layer.shadowOffset = CGSize(width: x, height: y)
        layer.shadowRadius = blur / 2.0
        if spread == 0 {
            layer.shadowPath = UIBezierPath(roundedRect: layer.bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornorRadius, height: cornorRadius)).cgPath
        } else {
            let dx = -(spread)
            let rect = layer.bounds.insetBy(dx: dx, dy: dx)
            layer.shadowPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: cornorRadius, height: cornorRadius)).cgPath
        }
    }
}

0

我真的很喜欢这里发布的答案和评论中的建议。这是我修改那个解决方案的方法:

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

使用方法:

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

-2

这可能需要一些挖掘历史,但也许有人曾经遇到过同样的问题。我使用了已被接受的答案中的代码示例。然而,效果却大不相同: - y 值必须约为绘图中相同值的一半 - 我尝试在导航栏上应用阴影,效果非常不同——即使使用与绘图相同的值,也几乎看不见。

因此,似乎这种方法完全不能反映绘图参数。 有什么提示吗?


这不是答案 - 评论应该放在评论区! - Dave Y

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接