如何在UIView下绘制阴影?

358

我正在尝试在Cocoa Touch中的UIView下绘制底部的阴影。我了解应该使用CGContextSetShadow()来绘制阴影,但是Quartz 2D编程指南有点含糊:

  1. 保存图形状态。
  2. 调用函数CGContextSetShadow,传递适当的值。
  3. 执行所有要应用阴影的绘图。
  4. 恢复图形状态。

我已经在UIView子类中尝试了以下内容:

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    CGContextRestoreGState(currentContext);
    [super drawRect: rect];
}

但这对我没有用,我有点困惑(a)下一步该怎么做,(b)是否需要对我的UIView做任何处理才能使其工作?

16个回答

788

一种更为简单的方法是在初始化时设置视图的一些层属性:

self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

你需要导入QuartzCore。

#import <QuartzCore/QuartzCore.h>

4
请注意,这仅适用于 iOS 3.2 及以上版本,因此如果您的应用程序应该在旧版本上工作,您必须使用Christian的解决方案或者在视图后面使用静态图像(如果可行)。 - florianbuerger
119
这个解决方案还需要在.h文件中添加 #import <QuartzCore/QuartzCore.h>" - MusiGenesis
26
masksToBounds 设置为 NO 会取消 cornerRadius,对吗? - pixelfreak
4
要解决这个问题,需要在图层上设置backgroundColor,并使视图透明。 - pixelfreak
@JamieTaylor:引用self.layer以前需要包含QuartzCore。 - MusiGenesis
显示剩余6条评论

234
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

这会使应用程序变慢。 只要您的视图是明显的矩形,添加以下行可以提高性能:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;

1
值得注意的是,这种优化只有在您的视图可见矩形时才有用。 - Benjamin Dobell
2
只需在其他行的基础上添加这一行即可。 - christophercotton
11
绘制阴影是一项昂贵的操作,所以如果您的应用程序允许其他界面方向,并且您开始旋转设备,则在动画过程中没有明确指定阴影路径的情况下,阴影需要被渲染多次。这将因形状而异,可能会明显减慢动画速度。 - Peter Pajchl
9
这句话只适用于矩形,但你也可以创建非矩形路径。例如,如果你有一个圆角矩形,可以使用bezierPathWithRoundedRect:cornerRadius:方法。 - Dan Dyer
1
@Dio,它的运行速度很快,不会使应用程序冻结,但是有一个问题,当我改变方向时,UIBezierPath阴影无法正常工作。请给我一些建议如何在方向上进行管理? - Hitarth
显示剩余4条评论

162

同样的解决方案,但是提醒一下:你可以直接在Storyboard中定义阴影。

例如:

输入图片描述


2
很遗憾,我认为CGColor在Storyboard中是无法实现的 :( - Antzi
1
只需为 UIViewCGLayer 定义一个类别,该类别是 CGColor 属性的 UIColor 包装器 ;) - DeFrenZ
8
为方便人们进行复制和粘贴:layer.masksToBound,layer.shadowOffset,layer.shadowRadius,layer.shadowOpacity。此处打错字可能会让你出局。 - etayluz
3
图层的shadowOpacity值应该是0.5而不是0,5。 - Desert Rose
1
你可以使用shadowUIColor而不是shadowColor。 - Amr Angry
显示剩余6条评论

99
在您当前的代码中,您保存了当前上下文的,将其配置为绘制阴影..然后将其恢复回先前配置阴影之前的状态。最后,您调用超类的实现。
任何应受阴影设置影响的绘图都需要在之后进行。
CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);

但是之前

CGContextRestoreGState(currentContext);

如果你想让超类的drawRect:方法被包含在一个阴影中,那么你可以尝试按照以下方式重新排列你的代码:

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    [super drawRect: rect];
    CGContextRestoreGState(currentContext);
}

45

您可以尝试这个...您可以玩弄值。 shadowRadius决定模糊程度。shadowOffset决定阴影的位置。

Swift 2.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.CGPath

Swift 3.0

(翻译:Swift 3.0)
let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height 
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height)) 
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.cgPath

使用扩展语法的示例

使用扩展语法的示例

创建基本阴影

    demoView.layer.cornerRadius = 2
    demoView.layer.shadowColor = UIColor.blackColor().CGColor
    demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
    demoView.layer.shadowOpacity = 0.5 
    demoView.layer.shadowRadius = 5.0 //Here your control your blur

Swift 2.0中的基本阴影示例

OUTPUT


19

使用Interface Builder的简洁解决方案

在您的项目中添加一个名为UIView.swift的文件(或将以下内容粘贴到任何文件中):

import UIKit

@IBDesignable extension UIView {

    /* The color of the shadow. Defaults to opaque black. Colors created
    * from patterns are currently NOT supported. Animatable. */
    @IBInspectable var shadowColor: UIColor? {
        set {
            layer.shadowColor = newValue!.CGColor
        }
        get {
            if let color = layer.shadowColor {
                return UIColor(CGColor:color)
            }
            else {
                return nil
            }
        }
    }

    /* The opacity of the shadow. Defaults to 0. Specifying a value outside the
    * [0,1] range will give undefined results. Animatable. */
    @IBInspectable var shadowOpacity: Float {
        set {
            layer.shadowOpacity = newValue
        }
        get {
            return layer.shadowOpacity
        }
    }

    /* The shadow offset. Defaults to (0, -3). Animatable. */
    @IBInspectable var shadowOffset: CGPoint {
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
    }

    /* The blur radius used to create the shadow. Defaults to 3. Animatable. */
    @IBInspectable var shadowRadius: CGFloat {
        set {
            layer.shadowRadius = newValue
        }
        get {
            return layer.shadowRadius
        }
    }
}

然后,在“属性检查器(Utilities Panel > Attributes Inspector)”中,此选项将适用于Interface Builder中每个视图:

属性检查器

现在您可以轻松设置阴影。

注意:
- 阴影不会在IB中出现,只会在运行时出现。
- 如Mazen Kasser所说:

对于那些无法让其正常工作的人[...]请确保未启用剪辑子视图(clipsToBounds


这是正确的答案 - 面向未来接口的配置优于代码。 - Crake
这个解决方案导致了一个不工作的行为,伴随着以下警告信息(每个属性都有一个):Failed to set (shadowColor) user defined inspected property on (UICollectionViewCell): [<UICollectionViewCell> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key shadowColor. - Xvolks
1
我不得不导入 UIKit 才能使其正常工作,XCode 创建的基本 Foundation 导入是不够的,但编译没有问题。我应该复制整个源代码。感谢 Axel 提供这个好的解决方案。 - Xvolks

13

我将其作为我的工具之一。通过这个工具,我们不仅可以设置阴影,还可以为任何UIView获取圆角。此外,您还可以设置喜欢的阴影颜色。通常情况下,黑色是首选,但有时候背景非白色时,您可能需要其他颜色。以下是我使用的内容 -

in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer 
              radius:(float)r 
              shadow:(BOOL)s
{
    [viewLayer setMasksToBounds:YES];
    [viewLayer setCornerRadius:r];        
    [viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
    [viewLayer setBorderWidth:1.0f];
    if(s)
    {
        [viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
        [viewLayer setShadowOffset:CGSizeMake(0, 0)];
        [viewLayer setShadowOpacity:1];
        [viewLayer setShadowRadius:2.0];
    }
    return;
}

使用它需要调用此方法 - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];


7

Swift 3

extension UIView {
    func installShadow() {
        layer.cornerRadius = 2
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 1)
        layer.shadowOpacity = 0.45
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath
        layer.shadowRadius = 1.0
    }
}

如果您采用这种方法,我建议添加参数,以便您可以调整值,使其具有动态性。 - Josh O'Connor
它没有给我圆角,我做错了什么吗? - Nikhil Manapure
@NikhilManapure 没有任何反应,这可能是因为函数 installShadow() 没有从 viewDidLoad()viewWillAppear() 调用。 - neoneye
我是从视图的重载draw方法中调用它的。阴影显示正确,但圆角没有显示出来。 - Nikhil Manapure
@NikhilManapure 没有圆角边框,这可能是因为视图是 UIStackView,它只进行布局而没有视图。您可能需要插入一个常规的 UIView 作为所有内容的容器。 - neoneye
@neoneye 这不是 UIStackView :( 但我会尽快找到并回复。 - Nikhil Manapure

6
如果您想使用StoryBoard并且不想在运行时输入属性,您可以轻松地创建一个视图扩展,并使它们可在StoryBoard中使用。
第一步:创建扩展。
extension UIView {

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

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

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

@IBInspectable var maskToBound: Bool {
    get {
        return layer.masksToBounds
    }
    set {
        layer.masksToBounds = newValue
    }
}
}

第二步。您现在可以在Storyboard中使用这些属性storyboard image


4

使用IBDesignable和IBInspectable在Swift 4中制作阴影效果

如何使用它

演示

Sketch和Xcode并排显示

阴影示例

代码

@IBDesignable class ShadowView: UIView {

    @IBInspectable var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }

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

    @IBInspectable var shadowOffset: CGPoint {
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }

     }

    @IBInspectable var shadowBlur: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue / 2.0
        }
    }

    @IBInspectable var shadowSpread: CGFloat = 0 {
        didSet {
            if shadowSpread == 0 {
                layer.shadowPath = nil
            } else {
                let dx = -shadowSpread
                let rect = bounds.insetBy(dx: dx, dy: dx)
                layer.shadowPath = UIBezierPath(rect: rect).cgPath
            }
        }
    }
}

输出结果

演示输出


你可以通过去除属性的阴影前缀来改善代码。ShadowView的目的在于清晰地表达阴影操作。另外一件事是你可以向这个类中添加圆角半径。(现今,大多数阴影视图都涉及到圆角半径) - mathema

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