给UIView顶部添加内阴影

24

我查了一下,但是找不到如何在Swift中为UIView添加内阴影,只能添加从上到下的顶部阴影。在Swift中添加内圆的最佳方法是什么?

编辑:我在SO上找到了一些问题和答案,但它们要么是Obj-C或者看起来太复杂。我只是想找一个更 Swift 的方式,如果有的话。

我想要实现以下效果:

enter image description here


3
https://github.com/inamiy/YIInnerShadowView - Arbitur
@Arbitur,你的链接是Objective-C的,我无法翻译。在它的SO答案中,它看起来是一段相当复杂的代码,并且涉及到各个方面... 它也没有使用像CGSizeMake这样简单的东西,这就是为什么我想问一下的原因。我只是在寻找一种更Swifty的方式。 - senty
1
如果您想确保阴影仅沿着上边框保持,无论模糊半径/偏移量等如何,并且不会渗入其他边框,最好的方法是添加一个UIImageView作为子视图,粘贴到顶部边框并沿整个宽度拉伸。如果您需要在侧边缘进行特殊处理(就像您的屏幕截图所示),也许具有插图的可拉伸图像可以解决问题。 - Nicolas Miari
虽然也许更聪明的做法是覆盖drawRect(_:)并使用Core Graphics完成所有操作。没有多余的视图,不需要图像资源,但需要更高级的代码。 - Nicolas Miari
@NicolasMiari 我认为那将是一个不错的解决方法,但不是答案。谢谢;我很想学习答案 :) - senty
7个回答

28

这是我迅速编写的纯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点的从黑到白的渐变。

完整的代码和一个示例UIViewController,让您可以切换视图四个角落的阴影,可在https://github.com/jrtibbetts/Tenebrae找到。我还对EdgeShadowLayer进行了相当详细的记录。

targetViewview 在这里分别代表什么?它们不是一样的吗? - Nagendra Rao
1
更新了答案。 - Nagendra Rao
你能否使用 UIColor.clear 作为 toColor,这样第二个颜色就会是透明的?这样它就可以独立于底层视图的颜色。 - Lou Valencia
当然可以;我不知道为什么不行。 - NRitH
我已经实现了它。请查看下面的帖子以获取代码和截图。 - Lou Valencia

16

我曾经使用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)
    }
}

1
迄今为止最好的答案。在我看来,其他答案使用了渐变,这不是Core Graphics中绘制阴影的方式。 - beyowulf
6
self.shadowLayer 是从哪里来的? - Jon Vogel
1
@JonVogel 可能只是一个 CALayer 属性,用于跟踪阴影是否已经添加以及稍后在视图的生命周期中访问和编辑它。如果您只想添加一次阴影,则删除检查和对其的赋值是可以的。 - kevin
self.shadowLayer 似乎是缺失的一部分。你能解释一下它来自哪里吗? - Jayprakash Dubey
1
@Jayprakash Dubey 只是一个属性,例如私有变量 shadowLayer: CALayer? - J.Hunter

8

我对@anoop4real所做的修改进行了微调,将toColor设置为清晰,并使界面更符合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

views with inner shadow


1
你的 removeAllShadows 方法会移除 UIView 的所有子层!为了正确使用分类,你应该在 addShadow 中添加关联对象,在 removeShadows 中删除它们,或者直接从你的实现中删除这个 remove 方法。 - iago849
这会产生一种乘法效应,其中阴影彼此重叠。即角落变得更暗。 - Womble
只有在UIcollectionviewcell中点击单元格时,底部和右侧才会显示。 - AmitTank

6
我更新了@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()
            }
        }
    }

}

Shadow


5

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

我重写了@NRitH的Swift 3解决方案,并进行了轻微的重构:

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

0

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

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

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

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