【问题标题】:How to control shadow spread and blur?如何控制阴影扩散和模糊?
【发布时间】:2016-03-20 01:45:43
【问题描述】:

我在草图中设计了 UI 元素,其中一个具有模糊 1 和散布 0 的阴影。我查看了视图图层属性的文档,图层没有任何名为散布或模糊的东西,或任何等效的东西(唯一的控制仅仅是shadowOpacity)。如何控制模糊和扩散之类的东西?

这是我在 Sketch 中的设置:

这是我希望我的影子看起来的样子:

这是目前的样子:

注意,你必须点击图片才能真正看到阴影。

我的代码如下:

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 来绘制阴影,这可能是相关的)都是相关的,我不明白为什么要删除它们。
  • 你只想要那个白色视图的阴影,对吗?
  • 看起来 Sketch 和 CoreAnimation 框架有不同的指标,因为它在 iOS 和具有相同参数的 Sketch 中看起来总是不同的。
  • 啊。与使用与 iOS 工作方式几乎没有相似之处的工具的设计师一起工作的乐趣。如果您使用 PaintCode 而不是 Sketch 之类的东西,它不仅会像 iOS 一样工作,而且还会为您提供所需的代码。 :-)
  • 如果你在草图中同时设置了半径和模糊怎么办?

标签: ios swift uiview core-graphics


【解决方案1】:

以下是如何以近乎完美的精度将所有 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
    }
  }
}

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

您可以通过以下方式轻松做到这一点:

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

或更简洁地说:

myView.layer.applySketchShadow(y: 0)

例子:

左:iPhone 8 UIView 截图;右:草图矩形。

注意:

  • 使用非零 spread 时,它会根据 CALayer 的 bounds 对路径进行硬编码。如果图层的边界发生变化,您可能需要再次调用 applySketchShadow() 方法。

【讨论】:

  • @Senseful 我很想知道你是如何实现这个公式的?!
  • 你忘了设置maskToBounds = false
  • 你怎么知道 blur = 4 转换为 shadowRadius 4 / 2.0 ?我想了解它们之间的关系。谢谢。
  • 正如 ScottyBlades 上面提到的,如果没有 maskToBounds = false,这将无法工作。失败的一个例子是将此阴影应用于UIButton
  • 应该是shadowRadius = blur / UIScreen.main.scale,而不是硬编码blur / 2.0?或者,还有其他理由除以 2 吗? @matchifang 你知道了吗?
【解决方案2】:

你可以试试这个......你可以玩这些价值观。 shadowRadius 指示模糊量。 shadowOffset 指示阴影的去向。

斯威夫特 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

斯威夫特 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

传播示例

创建基本阴影

    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 中的基本阴影示例

【讨论】:

  • 信息有误,offset不是spread的控制,是x和y的控制。
  • @Quantaliinuxite 我不小心互换了我已更新答案的 cmets
  • 对,现在我的问题的核心来了:我如何控制传播?
  • @Quantaliinuxite 更新的代码现在将 2.1 更改为您需要的点差量
  • 感谢您的回答。在这里我无法理解 shadowOpacity(为什么使用它)。
【解决方案3】:

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

草图和 XCODE 并排

代码

@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
  • 你为什么要乘以或除以阴影半径?有人可以解释一下吗
【解决方案4】:

这段代码对我来说效果很好:

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 的阴影半径匹配。
  • 最终对我们来说足够接近的是将核心动画阴影半径的 Sketch 中的模糊值减半。
  • spread 和 offset 是不同的参数。不要让人们感到困惑!
【解决方案5】:

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

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

我稍后会发布一些示例,但这对我来说适用于圆形视图。

【讨论】:

  • 这个“几乎”有效,但是当您使用 1xy 0 shapeLayer.applyShadow(color: .black, alpha: 1, x: 0, y: 0, blur: 0, spread: 1, path: path) 的传播时,传播是错误的。在这种情况下,阴影有一个偏移量,而它不应该。这里三角形的阴影应该向各个方向扩散 1px ibb.co/YjwRb9B
【解决方案6】:

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

【讨论】:

    【解决方案7】:

    @Senseful 's answer 稍作改动,就可以很好地适应我的项目。

    • 支持阴影角半径(​​对于一些圆角视图)
    • 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
            }
        }
    }
    

    【讨论】:

      【解决方案8】:

      我真的很喜欢 the answer posted here 和 cmets 中的建议。这就是我修改该解决方案的方式:

      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)
      

      【讨论】:

        【解决方案9】:

        这可能是历史上的一点挖掘,但也许有些人有同样的问题。我使用了已接受答案中的代码示例。但是效果却大相径庭: - y 值必须是草图中相同值的一半左右 - 我尝试在导航栏上应用阴影,但效果非常不同 - 使用与草图相同的值时几乎看不到。

        因此,该方法似乎完全没有反映草图参数。 有什么提示吗?

        【讨论】:

        • 这不是答案 - cmets 进入 cmets!
        猜你喜欢
        • 2020-05-06
        • 2017-11-17
        • 1970-01-01
        • 2013-12-16
        • 1970-01-01
        • 2019-01-10
        • 2015-09-17
        • 1970-01-01
        • 2018-05-20
        相关资源
        最近更新 更多