向我的 UIView 添加阴影的最佳方法是啥

Posted

技术标签:

【中文标题】向我的 UIView 添加阴影的最佳方法是啥【英文标题】:What's the best way to add a drop shadow to my UIView向我的 UIView 添加阴影的最佳方法是什么 【发布时间】:2012-04-03 09:47:52 【问题描述】:

我正在尝试为彼此叠加的视图添加阴影,视图折叠允许看到其他视图中的内容,在这种情况下,我想保持view.clipsToBounds ON 以便当视图折叠他们的内容被剪裁了。

这似乎让我很难为图层添加阴影,因为当我打开clipsToBounds 时,阴影也会被剪裁。

我一直在尝试操纵 view.frameview.bounds 以便向框架添加阴影,但允许边界足够大以包含它,但是我没有运气。

这是我用来添加阴影的代码(这只适用于clipsToBounds OFF,如图所示)

view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;

这是应用到最浅灰色层的阴影的屏幕截图。希望这可以让您了解如果clipsToBounds 关闭,我的内容将如何重叠。

如何为我的UIView 添加阴影并保持内容被剪辑?

编辑:只是想补充一点,我也尝试过使用带有阴影的背景图像,效果很好,但是我仍然想知道最好的编码解决方案。

【问题讨论】:

【参考方案1】:

Wasabii 在 Swift 2.3 中的回答:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.CGPath

在 Swift 3/4/5 中:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.cgPath

如果您使用 AutoLayout,请将此代码放入 layoutSubviews()。

在 SwiftUI 中,这一切都容易得多:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)

【讨论】:

感谢layoutSubviews 在 viewDidLayoutSubviews() 中甚至更好 更喜欢 swift 结构初始化器而不是 Make 变体,即CGSize(width: 0, height: 0.5) 我将此代码添加到 layoutSubviews(),它仍然没有在 IB 中呈现。还有其他我必须选择的设置吗? 谢谢。我正在使用自动布局,我想我自己 - 好吧.. view.bounds 没有被创建,很明显shadowPath 不能引用视图的矩形,而是一些 CGRectZero。不管怎样,把这个放在layoutSubviews 下终于解决了我的问题!【参考方案2】:

所以是的,您应该更喜欢 shadowPath 属性以提高性能,而且: 来自CALayer.shadowPath的头文件

使用该属性显式指定路径通常会 *提高渲染性能,共享相同的路径 * 跨多个层引用

一个鲜为人知的技巧是跨多个层共享相同的引用。当然,它们必须使用相同的形状,但这在表格/集合视图单元格中很常见。

我不知道为什么如果你共享实例它会变得更快,我猜它缓存了阴影的渲染并且可以将它重用于视图中的其他实例。我想知道使用

是否会更快

【讨论】:

【参考方案3】:

您也可以从情节提要中为您的视图设置阴影

【讨论】:

【参考方案4】:

您可以为 UIView 创建扩展以在设计编辑器中访问这些值

extension UIView

    @IBInspectable var shadowOffset: CGSize
        get
            return self.layer.shadowOffset
        
        set
            self.layer.shadowOffset = newValue
        
    

    @IBInspectable var shadowColor: UIColor
        get
            return UIColor(cgColor: self.layer.shadowColor!)
        
        set
            self.layer.shadowColor = newValue.cgColor
        
    

    @IBInspectable var shadowRadius: CGFloat
        get
            return self.layer.shadowRadius
        
        set
            self.layer.shadowRadius = newValue
        
    

    @IBInspectable var shadowOpacity: Float
        get
            return self.layer.shadowOpacity
        
        set
            self.layer.shadowOpacity = newValue
        
    

【讨论】:

如何在界面生成器中引用该扩展 IBInspectable 使其在界面构建器中可用 我也可以在 Objective C 中实现这一点吗? 如果你想实时预览(在界面生成器中),在这里你可以找到一篇关于它的好文章spin.atomicobject.com/2017/07/18/swift-interface-builder【参考方案5】:

试试这个:

UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;

首先:用作shadowPathUIBezierPath 至关重要。如果您不使用它,一开始您可能不会注意到差异,但敏锐的眼睛会观察到在旋转设备和/或类似事件期间发生的某种滞后。这是一项重要的性能调整。

特别针对您的问题:重要的一行是view.layer.masksToBounds = NO。它禁用了对视图层的子层的裁剪,这些子层超出了视图的边界。

对于那些想知道masksToBounds(在层上)和视图自己的clipToBounds 属性之间有什么区别的人来说:真的没有。切换一个会对另一个产生影响。只是抽象级别不同。


斯威夫特 2.2:

override func layoutSubviews()

    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.blackColor().CGColor
    layer.shadowOffset = CGSizeMake(0.0, 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.CGPath


斯威夫特 3:

override func layoutSubviews()

    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.cgPath

【讨论】:

谢谢,我尝试了您的代码,然后尝试将 masksToBounds = NO; 添加到我的原始代码中 - 两次尝试我都将 clipsToBounds = YES; 保持打开 - 都未能剪切内容。这是您的示例发生的情况的屏幕截图> youtu.be/tdpemc_Xdps 关于如何让投影拥抱圆角而不是渲染成圆角仍然是正方形的任何想法? @JohnErck 试试这个:UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];。未经测试,但应该会产生您想要的结果。 我了解到,在为视图设置动画时,阴影在移动时会留下灰色轨迹。删除 shadowPath 线解决了这个问题 @shoe 因为任何时候视图大小发生变化,都会调用layoutSubviews()。如果我们不通过调整shadowPath 以反映当前大小来补偿那个地方,我们将有一个调整大小的视图,但阴影仍将是旧的(初始)大小,并且不会显示或窥视它的位置不应该。【参考方案6】:

关于 viewWillLayoutSubviews:

override func viewWillLayoutSubviews() 
    sampleView.layer.masksToBounds =  false
    sampleView.layer.shadowColor = UIColor.darkGrayColor().CGColor;
    sampleView.layer.shadowOffset = CGSizeMake(2.0, 2.0)
    sampleView.layer.shadowOpacity = 1.0

使用 UIView 的扩展:

extension UIView 

    func addDropShadowToView(targetView:UIView? )
        targetView!.layer.masksToBounds =  false
        targetView!.layer.shadowColor = UIColor.darkGrayColor().CGColor;
        targetView!.layer.shadowOffset = CGSizeMake(2.0, 2.0)
        targetView!.layer.shadowOpacity = 1.0
    

用法:

sampleView.addDropShadowToView(sampleView)

【讨论】:

执行targetView: UIView 并去掉刘海。 为什么不使用self?对我来说似乎更方便。 一个更好的办法是去掉targetView作为参数,用self代替targetView。这消除了冗余并允许人们像这样调用它:sampleView.addDropShadowToView() Max Nelson 的评论很棒。我的建议是使用if let 语法而不是!【参考方案7】:

诀窍是正确定义视图层的masksToBounds 属性:

view.layer.masksToBounds = NO;

它应该可以工作。

(Source)

【讨论】:

以上是关于向我的 UIView 添加阴影的最佳方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

在 UIImageView 后面创建阴影的最佳方法是啥

带有圆角和阴影的 Swift uiview 不起作用

为选定的边缘 UIView 添加阴影

iOS Swift如何为透明的UIView添加阴影

向使用 SnapKit 定位的 UIView 添加阴影

使用 UIBezierPath 向 UIView 添加阴影