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


108

我试图向彼此叠置的视图添加阴影,这些视图折叠以允许查看其他视图中的内容,因此我想保持view.clipsToBounds打开状态,以便在视图折叠时它们的内容被剪切。

这似乎使我很难在图层上添加阴影,因为当我打开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并保持剪切内容?

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

Answers:


280

试试这个:

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;

首先:在UIBezierPath使用shadowPath是至关重要的。如果您不使用它,一开始可能不会注意到有什么不同,但是敏锐的眼睛会观察到在旋转设备等事件期间发生一定的滞后。这是一项重要的性能调整。

关于您的问题:重要的是view.layer.masksToBounds = NO。它禁用对视图图层的子图层的裁剪,该子图层的扩展范围超过视图的边界。

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


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

1
谢谢,我尝试了您的代码,然后尝试将其添加masksToBounds = NO;到原始文件中-两次尝试都保持clipsToBounds = YES;打开状态-都无法剪辑内容。这是您的示例发生的事情的屏幕截图> youtu.be/tdpemc_Xdps
Wez

1
关于如何获得阴影以拥抱圆角而不是渲染该角落好像仍然是正方形的任何想法吗?
John Erck 2014年

7
@JohnErck试试这个:UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];。未经测试,但应产生所需的结果。
pkluz 2014年

1
我了解到,为视图设置动画时,阴影在移动时会留下灰色轨迹。删除shadowPath线解决了这一问题
ishahak 2015年

1
@shoe,因为视图的任何时间大小都会改变,因此会layoutSubviews()被调用。如果我们不通过调整shadowPath来反映当前大小而在该位置进行补偿,我们将具有调整大小的视图,但阴影仍将是旧的(初始)大小,并且不会显示或窥视不应显示的位置。
pkluz

65

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)

11
感谢您的注意layoutSubviews
Islam Q.

1
甚至更好的viewDidLayoutSubviews()
Max MacLeod 2015年

1
比Make变体更喜欢快速的结构初始化程序,即CGSize(width: 0, height: 0.5)
Oliver Atkinson

我将此代码添加到layoutSubviews(),它仍然没有在IB中呈现。我还必须选择其他设置吗?

谢谢。我当时使用的是自动版式,我想自己-很好。. view.bounds显然没有创建,shadowPath无法引用视图的矩形,而是引用了某些矩形CGRectZero。无论如何,layoutSubviews解决这个问题终于解决了我的问题!
devdc

13

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

view.layer.masksToBounds = NO;

它应该工作。

来源


13

您可以为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
        }
    }
}

如何在界面生成器中引用该扩展
Amr Angry

IBInspectable使它在界面生成器中可用
Nitesh

我也可以在目标C中实现吗?
Harish Pathak

2
如果您想实时预览(在界面生成器中),则可以在此处找到关于它的好文章 spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill '18


2

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

1
只需执行targetView: UIView并消除刘海。
bluebamboo

6
为什么不使用self?对我来说似乎更方便。
LinusGeffarth '16

3
更好的方法是删除targetView作为参数,并使用self代替targetView。这消除了冗余并允许这样调用:sampleView.addDropShadowToView()
maxcodes

马克斯·纳尔逊的评论很棒。我的建议是使用if let语法,而不是!
Johnny

0

因此,是的,您应该更喜欢shadowPath属性来提高性能,而且:从CALayer.shadowPath的头文件中

使用此属性显式指定路径通常可以*提高渲染性能,因为可以跨多层共享同一路径*参考

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.