在UIView外绘制投影阴影

7

背景

我有一个拥有以下属性的UIView

  • alpha = 1
  • 背景颜色为白色,并带有0.35的透明度
  • 圆角
  • 阴影

代码

这是我如何创建阴影的:(UIView扩展)

self.layer.masksToBounds = false
self.layer.shadowColor = UIColor.darkGrayColor().CGColor
self.layer.shadowOffset = CGSizeMake(0, 5)
self.layer.shadowOpacity = 0.35
self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).CGPath

结果

这将导致以下结果:

当前结果

...而我不想看到视图下面的阴影像这样:

期望的结果

问题

如何仅在视图外部绘制阴影,以便它不可见在下面?

提前感谢!


你好,我不确定这是否符合您的要求,但也许在Photoshop中创建一张图片可以满足您的需求? - Link Openheim
@LinkOpenheim 这是我采用的解决方法,但如果可能的话,我更愿意通过编程来创建它。 - LinusGeffarth
当然,我所知道的最好方法是为阴影创建一个图层蒙版。你知道怎么做吗? - Link Openheim
不太对。您能否展示一下示例代码? - LinusGeffarth
现在做这件事非常容易。 - Fattie
3个回答

6

这可能比必要的要复杂,但是这里有一个解决方案。

使用以下方法扩展 UIView

extension UIView {
    // Note: the method needs the view from which the context is taken as an argument.
    func dropShadow(superview: UIView) {
        // Get context from superview
        UIGraphicsBeginImageContext(self.bounds.size)
        superview.drawViewHierarchyInRect(CGRect(x: -self.frame.minX, y: -self.frame.minY, width: superview.bounds.width, height: superview.bounds.height), afterScreenUpdates: true)
        let image = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()

        // Add a UIImageView with the image from the context as a subview
        let imageView = UIImageView(frame: self.bounds)
        imageView.image = image
        imageView.layer.cornerRadius = self.layer.cornerRadius
        imageView.clipsToBounds = true
        self.addSubview(imageView)

        // Bring the background color to the front, alternatively set it as UIColor(white: 1, alpha: 0.2)
        let brighter = UIView(frame: self.bounds)
        brighter.backgroundColor = self.backgroundColor ?? UIColor(white: 1, alpha: 0.2)
        brighter.layer.cornerRadius = self.layer.cornerRadius
        brighter.clipsToBounds = true
        self.addSubview(brighter)

        // Set the shadow
        self.layer.masksToBounds = false
        self.layer.shadowColor = UIColor.darkGrayColor().CGColor
        self.layer.shadowOffset = CGSizeMake(0, 5)
        self.layer.shadowOpacity = 0.35
        self.layer.shadowPath = UIBezierPath(roundedRect: self.bounds, cornerRadius: self.layer.cornerRadius).CGPath
    }
}

如果背景视图被命名为view,那么使用如下:

let shadowView = UIView(frame: CGRect(x: 100, y: 100, width: 300, height: 200))
shadowView.layer.cornerRadius = 15.0
shadowView.dropShadow(view)

view.addSubview(shadowView)

这将导致视图呈现如下所示: UIView 注意:由于这会导致图形上下文出现问题,因此不能在viewDidLoad中调用dropShadow方法。因此,请尽早在viewWillAppear中使用此方法以获得上述结果。
下面是背景视图的代码,以防有人想要在playgrounds中测试:
let view = UIView(frame: CGRect(x: 0, y: 0, width: 500, height: 400))
view.backgroundColor = UIColor.clearColor()

let color1 = UIColor(hue: 0.39, saturation: 0.7, brightness: 1.0, alpha: 1.0).CGColor
let color2 = UIColor(hue: 0.51, saturation: 0.9, brightness: 0.6, alpha: 1.0).CGColor

let gradient = CAGradientLayer()
gradient.frame = view.frame
gradient.colors = [color1, color2]
gradient.startPoint = CGPoint(x: 0, y: 0)
gradient.endPoint = CGPoint(x: 1, y: 1)
view.layer.insertSublayer(gradient, atIndex: 0)

看起来不错,但对我来说,这是结果 - LinusGeffarth
抱歉,可能应该澄清一下:superview参数需要“shadowView”获取其上下文的视图。因此,如果您希望它模仿名为backgroundView的背景,则需要将其作为参数传递给方法:shadowView.dropShadow(backgroundView) - xoudini
你所说的“模仿”具体是什么意思?我有一个带有圆角和不透明度的白色视图,我想要添加投影效果。它的父视图只是一个名为view的视图,因此我将其作为参数传递。但不知何故,这似乎是不正确的... - LinusGeffarth
刚刚添加了一个我用于得到这个精确结果的示例背景。所谓“模仿”,是指该方法需要带有图像或渐变或其他元素的背景,以从其上下文中创建图像。 - xoudini
好吧,也许我只是太蠢了。现在整个屏幕都变成了绿色。当我打印视图时,它告诉我它的框架大小是正确的。你介意分享一下你的项目吗? - LinusGeffarth
1
在 playground 中尝试一下 - 首先复制背景,然后是扩展和使用示例。这应该会稍微澄清一些,否则我们可能需要将其转移到聊天中 :) - xoudini

6
我通常使用视图组合来解决这种情况。不是在目标视图中设置阴影,而是创建一个ShadowView并将其放置在target视图后面,具有相同的框架。
这样做,我们可以遮罩阴影视图,以便只在其框架外绘制阴影。
阴影掩码的代码如下(完整代码如上):
      let maskLayer = CAShapeLayer()
      let path = CGMutablePath()
      path.addPath(UIBezierPath(roundedRect: bounds.inset(by: UIEdgeInsets.zero), cornerRadius: CGFloat(cornerRadius)).cgPath)
      path.addPath(UIBezierPath(roundedRect: bounds.inset(by: UIEdgeInsets(top: -offset.height - radius*2, left: -offset.width - radius*2, bottom: -offset.height - radius*2, right: -offset.width - radius*2)), cornerRadius: CGFloat(cornerRadius)).cgPath)
      maskLayer.backgroundColor = UIColor.black.cgColor
      maskLayer.path = path;
      maskLayer.fillRule = .evenOdd
      self.layer.mask = maskLayer

结果如下:

以下是结果:

遮罩阴影

完整的 playground 在这里可用:https://gist.github.com/llinardos/d1ede3b491efc8edac940c5ea8631c6f

这种效果在模糊背景时看起来非常漂亮。


1
了不起的@kanobius,这是唯一对我有用的答案。 - Pedro Antonio
https://dev59.com/R6Dia4cB1Zd3GeqPG6WQ#59092828 - Fattie

0
这是从这里找到的帖子进行了一些修改。我不知道你的层的具体规格,所以我创建了一个位于屏幕中央的圆形。您可以轻松地用您的层的规格替换圆形,我相信这将解决您的问题。如有任何进一步的指示,请随时发表评论! 导入UIKit
class ViewController: UIViewController {

override func viewDidLoad() {
    super.viewDidLoad()
    createOverlay(view.bounds)
}
func createOverlay(frame : CGRect)
{
    let overlayView = UIView(frame: frame)
    overlayView.alpha = 0.6
    overlayView.backgroundColor = UIColor.blackColor()
    self.view.addSubview(overlayView)

    let maskLayer = CAShapeLayer()

    let path = CGPathCreateMutable()

    let radius : CGFloat = 50.0
    let xOffset : CGFloat = view.bounds.width / 2
    let yOffset : CGFloat = view.bounds.height / 2

    CGPathAddArc(path, nil, overlayView.frame.width - xOffset, yOffset, radius, 0.0, 2 * 3.14, false)
    CGPathAddRect(path, nil, CGRectMake(0, 0, overlayView.frame.width, overlayView.frame.height))
    maskLayer.backgroundColor = UIColor.blackColor().CGColor
    maskLayer.path = path;
    maskLayer.fillRule = kCAFillRuleEvenOdd

    overlayView.layer.mask = maskLayer
    overlayView.clipsToBounds = true
}
}

照片 模拟器截图


这个是如何添加阴影的呢?感觉你可能误解了我的问题 :D - LinusGeffarth
这不会添加阴影,而是会减弱位于视图下方的阴影。如果我理解你的问题正确,你想在视图周围添加阴影,但不包括下方? - Link Openheim
我现在明白了,我确实误解了你的问题。请原谅我,让我想一个不同的解决方案。 - Link Openheim
没错。我希望投影可以在视图外部可见,但不在其下方。 - LinusGeffarth
2022年现在很容易:https://dev59.com/R6Dia4cB1Zd3GeqPG6WQ#59092828 - Fattie

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