【问题标题】:Adding inner shadow to top of UIView在 UIView 顶部添加内部阴影
【发布时间】:2016-10-06 17:48:49
【问题描述】:

我查了一下,但找不到如何向 UIView 添加内部阴影,对于 Swift,只有顶部(从上到下)。在 Swift 中添加内圈的最佳方法是什么?

编辑:我在 SO 上找到了一些问题和答案,但是它们要么在 obj-c 中,要么看起来很复杂。我只是在寻找一种更 Swifty 的方式,如果有的话

我想要达到的目标:

【问题讨论】:

  • @Arbitur,您的链接在 Objective-c 中,我无法翻译。在它的 SO 答案中,它看起来相当复杂的代码块及其所有方面......它也没有使用像CGSizeMake 这样简单的东西,这就是我想问它的原因。我只是在寻找一种更 Swifty 的方式
  • 如果你想确保阴影只停留在上边界,不管模糊半径/偏移量/等等。并且不会渗入其他边框,最好的办法是添加UIImageView 作为子视图,粘在顶部边框并沿整个宽度拉伸。如果您需要在侧边缘进行特殊处理(就像您的屏幕截图所暗示的那样),也许带有插图的可拉伸图像可以解决问题。
  • 虽然覆盖drawRect(_:) 并使用Core Graphics 做所有事情可能会更聪明。没有多余的额外视图,不需要图像资源,而是更高级的代码。
  • @NicolasMiari 我认为这将是一个很好的解决方法,但不能回答。谢谢;我很想知道答案:)

标签: ios swift uiview shadow


【解决方案1】:

Swift 5 扩展

extension UIView {
    func addInnerShadow() {
        let innerShadow = CALayer()
        innerShadow.frame = bounds
        
        // Shadow path (1pt ring around bounds)
        let radius = self.layer.cornerRadius
        let path = UIBezierPath(roundedRect: innerShadow.bounds.insetBy(dx: 2, dy:2), cornerRadius:radius)
        let cutout = UIBezierPath(roundedRect: innerShadow.bounds, cornerRadius:radius).reversing()
        
        path.append(cutout)
        innerShadow.shadowPath = path.cgPath
        innerShadow.masksToBounds = true
        
        // Shadow properties
        innerShadow.shadowColor = UIColor.black.cgColor
        innerShadow.shadowOffset = CGSize(width: 0, height: 0)
        innerShadow.shadowOpacity = 0.5
        innerShadow.shadowRadius = 2
        innerShadow.cornerRadius = self.layer.cornerRadius
        layer.addSublayer(innerShadow)
    }
}

【讨论】:

    【解决方案2】:

    如果您不介意使用clipsToBounds = true,您可以在视图边缘创建一个新的CALayer 偏移并将阴影添加到该视图。这就是 J.Hunter 的回答。

    J.Hunter 的代码添加了顶部阴影,这里我将其更新为 Swift 5 并添加到底部。

    斯威夫特 5:

    override func draw(_ rect: CGRect) {
      // Create Inner Shadow. Not sure about efficiency of this.
      // You may want to create a shadowLayer property
      //  and only run this code if it hasn't been created yet.
      let size = rect.size
      clipsToBounds = true // Don't want to see your fake view layer
      let innerShadowLayer: CALayer = CALayer()
      
      // Need to set a backgroundColor or it doesn't work
      innerShadowLayer.backgroundColor = UIColor.black.cgColor
      
      // Position your shadow layer (anchor point is in the center)
      //  on the edge of where your shadow needs to be.
      // In my case this moves the shadow layer to the
      //  bottom edge of my view
      innerShadowLayer.position = CGPoint(x: size.width / 2, y: size.height + (size.height / 2))
      
      // This could be smaller I think, just copying J.Hunter's code...
      innerShadowLayer.bounds = CGRect(x: 0, y: 0, width: size.width, height: size.height)
      
      // Normal shadow layer properties you'd use for an outer shadow
      innerShadowLayer.shadowColor = UIColor.black.cgColor
      innerShadowLayer.shadowOffset = CGSize(width: 0, height: 0)
      innerShadowLayer.shadowOpacity = 0.3
      innerShadowLayer.shadowRadius = 3
      
      layer.addSublayer(innerShadowLayer)
    }
    

    【讨论】:

      【解决方案3】:

      我使用 clear 作为toColor 调整了@anoop4real 所做的修改,并使界面更符合 CALayer 中的阴影设置,包括默认值,不透明度除外,默认情况下设置为 0.0。我选择了默认值 0.6,因为它看起来最自然。

      extension UIView {
          func addShadow(to edges: [UIRectEdge], radius: CGFloat = 3.0, opacity: Float = 0.6, color: CGColor = UIColor.black.cgColor) {
      
              let fromColor = color
              let toColor = UIColor.clear.cgColor
              let viewFrame = self.frame
              for edge in edges {
                  let gradientLayer = CAGradientLayer()
                  gradientLayer.colors = [fromColor, toColor]
                  gradientLayer.opacity = opacity
      
                  switch edge {
                  case .top:
                      gradientLayer.startPoint = CGPoint(x: 0.5, y: 0.0)
                      gradientLayer.endPoint = CGPoint(x: 0.5, y: 1.0)
                      gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: radius)
                  case .bottom:
                      gradientLayer.startPoint = CGPoint(x: 0.5, y: 1.0)
                      gradientLayer.endPoint = CGPoint(x: 0.5, y: 0.0)
                      gradientLayer.frame = CGRect(x: 0.0, y: viewFrame.height - radius, width: viewFrame.width, height: radius)
                  case .left:
                      gradientLayer.startPoint = CGPoint(x: 0.0, y: 0.5)
                      gradientLayer.endPoint = CGPoint(x: 1.0, y: 0.5)
                      gradientLayer.frame = CGRect(x: 0.0, y: 0.0, width: radius, height: viewFrame.height)
                  case .right:
                      gradientLayer.startPoint = CGPoint(x: 1.0, y: 0.5)
                      gradientLayer.endPoint = CGPoint(x: 0.0, y: 0.5)
                      gradientLayer.frame = CGRect(x: viewFrame.width - radius, y: 0.0, width: radius, height: viewFrame.height)
                  default:
                      break
                  }
                  self.layer.addSublayer(gradientLayer)
              }
          }
      
          func removeAllShadows() {
              if let sublayers = self.layer.sublayers, !sublayers.isEmpty {
                  for sublayer in sublayers {
                      sublayer.removeFromSuperlayer()
                  }
              }
          }
      }
      

      顶视图为默认设置,底部使用半径5.0显示更清晰。

      view1.addShadow([.top, .bottom, .left, .right])
      view2.addShadow([.top, .bottom, .left, .right], radius: 5.0)
      view2.backgroundColor = .orange
      

      【讨论】:

      • 您的 removeAllShadows 完全删除了 UIView 的所有子层!为了正确使用类别,您应该在 addShadow 中添加关联对象并在 removeShadows 中删除它们,或者只是从您的实现中删除此删除方法
      • 这会在阴影相互重叠的地方产生相乘效果。即角落变暗。
      • 底部和右侧仅在点击 UIcollectionviewcell 中的单元格时显示
      【解决方案4】:

      这是我制作的纯 Swift 版本:

      public class EdgeShadowLayer: CAGradientLayer {
      
          public enum Edge {
              case Top
              case Left
              case Bottom
              case Right
          }
      
          public init(forView view: UIView,
                      edge: Edge = Edge.Top,
                      shadowRadius radius: CGFloat = 20.0,
                      toColor: UIColor = UIColor.white,
                      fromColor: UIColor = UIColor.black) {
              super.init()
              self.colors = [fromColor.cgColor, toColor.cgColor]
              self.shadowRadius = radius
      
              let viewFrame = view.frame
      
              switch edge {
                  case .Top:
                      startPoint = CGPoint(x: 0.5, y: 0.0)
                      endPoint = CGPoint(x: 0.5, y: 1.0)
                      self.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: shadowRadius)
                  case .Bottom:
                      startPoint = CGPoint(x: 0.5, y: 1.0)
                      endPoint = CGPoint(x: 0.5, y: 0.0)
                      self.frame = CGRect(x: 0.0, y: viewFrame.height - shadowRadius, width: viewFrame.width, height: shadowRadius)
                  case .Left:
                      startPoint = CGPoint(x: 0.0, y: 0.5)
                      endPoint = CGPoint(x: 1.0, y: 0.5)
                      self.frame = CGRect(x: 0.0, y: 0.0, width: shadowRadius, height: viewFrame.height)
                  case .Right:
                      startPoint = CGPoint(x: 1.0, y: 0.5)
                      endPoint = CGPoint(x: 0.0, y: 0.5)
                      self.frame = CGRect(x: viewFrame.width - shadowRadius, y: 0.0, width: shadowRadius, height: viewFrame.height)
              }
          }
      
          required public init?(coder aDecoder: NSCoder) {
              fatalError("init(coder:) has not been implemented")
          }   
      }
      

      要使用它,

      let topShadow = EdgeShadowLayer(forView: targetView, edge: .Top)
      targetView.layer.addSublayer(topShadow)
      

      请注意,它默认为 20 点深的黑白渐变。

      可以在https://github.com/jrtibbetts/Tenebrae 找到完整的代码,其中包含一个示例UIViewController,可让您在视图的所有四个角上切换阴影。我还非常彻底地记录了EdgeShadowLayer

      【讨论】:

      • 这里的targetViewview 是什么?不都一样吗?
      • 更新了答案。
      • 你可以使用UIColor.clear 作为toColor 使第二种颜色是透明的吗?这样它就可以独立于底层视图的颜色。
      • 当然;我不明白为什么不。
      • 我实现了它。有关代码和屏幕截图,请参阅下面的帖子。
      【解决方案5】:

      我更新了@NRitH 的答案并对其进行了扩展,也对其进行了修改,以便您可以一次操作多个边缘

      用法

      myview.addShadow(to: [.top,.bottom], radius: 15.0)

      extension UIView{
      
          func addShadow(to edges:[UIRectEdge], radius:CGFloat){
      
              let toColor = UIColor(colorLiteralRed: 235.0/255.0, green: 235.0/255.0, blue: 235.0/255.0, alpha: 1.0)
              let fromColor = UIColor(colorLiteralRed: 188.0/255.0, green: 188.0/255.0, blue: 188.0/255.0, alpha: 1.0)
              // Set up its frame.
              let viewFrame = self.frame
              for edge in edges{
                  let gradientlayer          = CAGradientLayer()
                  gradientlayer.colors       = [fromColor.cgColor,toColor.cgColor]
                  gradientlayer.shadowRadius = radius
      
                  switch edge {
                  case UIRectEdge.top:
                      gradientlayer.startPoint = CGPoint(x: 0.5, y: 0.0)
                      gradientlayer.endPoint = CGPoint(x: 0.5, y: 1.0)
                      gradientlayer.frame = CGRect(x: 0.0, y: 0.0, width: viewFrame.width, height: gradientlayer.shadowRadius)
                  case UIRectEdge.bottom:
                      gradientlayer.startPoint = CGPoint(x: 0.5, y: 1.0)
                      gradientlayer.endPoint = CGPoint(x: 0.5, y: 0.0)
                      gradientlayer.frame = CGRect(x: 0.0, y: viewFrame.height - gradientlayer.shadowRadius, width: viewFrame.width, height: gradientlayer.shadowRadius)
                  case UIRectEdge.left:
                      gradientlayer.startPoint = CGPoint(x: 0.0, y: 0.5)
                      gradientlayer.endPoint = CGPoint(x: 1.0, y: 0.5)
                      gradientlayer.frame = CGRect(x: 0.0, y: 0.0, width: gradientlayer.shadowRadius, height: viewFrame.height)
                  case UIRectEdge.right:
                      gradientlayer.startPoint = CGPoint(x: 1.0, y: 0.5)
                      gradientlayer.endPoint = CGPoint(x: 0.0, y: 0.5)
                      gradientlayer.frame = CGRect(x: viewFrame.width - gradientlayer.shadowRadius, y: 0.0, width: gradientlayer.shadowRadius, height: viewFrame.height)
                  default:
                      break
                  }
                  self.layer.addSublayer(gradientlayer)
              }
      
          }
      
          func removeAllSublayers(){
              if let sublayers = self.layer.sublayers, !sublayers.isEmpty{
                  for sublayer in sublayers{
                      sublayer.removeFromSuperlayer()
                  }
              }
          }
      
      }
      

      【讨论】:

        【解决方案6】:

        我在 Swift 3 上重写了@NRitH 解决方案,也稍微重构了它:

        final class SideShadowLayer: CAGradientLayer {
            enum Side {
                case top,
                bottom,
                left,
                right
            }
        
            init(frame: CGRect, side: Side, shadowWidth: CGFloat,
                 fromColor: UIColor = .black,
                 toColor: UIColor = UIColor(red: 0, green: 0, blue: 0, alpha: 0),
                 opacity: Float = 0.5) {
                super.init()
        
                colors = [fromColor.cgColor, toColor.cgColor]
                self.opacity = opacity
        
                switch side {
                case .bottom:
                    startPoint = CGPoint(x: 0.5, y: 1.0)
                    endPoint = CGPoint(x: 0.5, y: 0.0)
                    self.frame = CGRect(x: 0, y: frame.height - shadowWidth, width: frame.width, height: shadowWidth)
        
                case .top:
                    startPoint = CGPoint(x: 0.5, y: 0.0)
                    endPoint = CGPoint(x: 0.5, y: 1.0)
                    self.frame = CGRect(x: 0, y: 0, width: frame.width, height: shadowWidth)
        
                case .left:
                    startPoint = CGPoint(x: 0.0, y: 0.5)
                    endPoint = CGPoint(x: 1.0, y: 0.5)
                    self.frame = CGRect(x: 0, y: 0, width: shadowWidth, height: frame.height)
        
                case .right:
                    startPoint = CGPoint(x: 1.0, y: 0.5)
                    endPoint = CGPoint(x: 0.0, y: 0.5)
                    self.frame = CGRect(x: frame.width - shadowWidth, y: 0, width: shadowWidth, height: frame.height)
                }
            }
        
            required init?(coder aDecoder: NSCoder) {
                super.init(coder: aDecoder)
            }
        }
        

        【讨论】:

          【解决方案7】:

          我使用 Objective-C 实现 UIView 的内部阴影。我尝试将代码翻译成 swift。请原谅我糟糕的 swift 语法

          你可以在 UIView.didMoveToSuperview 中调用下面的函数

          func drawShadow() {
              if nil == self.shadowLayer {
                  let size = self.frame.size
                  self.clipsToBounds = true
                  let layer: CALayer = CALayer()
                  layer.backgroundColor = UIColor.lightGrayColor().CGColor
                  layer.position = CGPointMake(size.width / 2, -size.height / 2 + 0.5)
                  layer.bounds = CGRectMake(0, 0, size.width, size.height)
                  layer.shadowColor = UIColor.darkGrayColor().CGColor
                  layer.shadowOffset = CGSizeMake(0.5, 0.5)
                  layer.shadowOpacity = 0.8
                  layer.shadowRadius = 5.0
                  self.shadowLayer = layer
          
                  self.layer.addSublayer(layer)
              }
          }
          

          【讨论】:

          • 迄今为止最好的答案。恕我直言。其他答案使用渐变,这不是 Core Graphics 中绘制阴影的方式。
          • self.shadowLayer 来自哪里?
          • @JonVogel 可能只是一个 CALayer 属性,用于跟踪是否添加了阴影,并在视图的生命周期中稍后访问和编辑它。如果您想要做的只是添加一次阴影,删除检查和分配就可以了。
          • self.shadowLayer 似乎缺少一块。你能解释一下它是从哪里来的吗?
          • @Jayprakash Dubey 只是一个属性,比如 private var shadowLayer: CALayer?
          猜你喜欢
          • 2014-07-30
          • 1970-01-01
          • 1970-01-01
          • 2020-07-22
          • 1970-01-01
          • 1970-01-01
          • 2012-03-02
          • 2019-01-17
          • 2013-04-25
          相关资源
          最近更新 更多