ios 带有阴影、圆角和自定义 drawRect 的 UIView

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/25591389/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 02:07:44  来源:igfitidea点击:

UIView with shadow, rounded corners and custom drawRect

iosobjective-cuiviewswiftdrawrect

提问by BadmintonCat

I have to create a custom UIViewthat will have round corners, a border, a shadow and its drawRect()method is overridden to provide custom drawing code with which several straight lines are drawn into the view (I need to use a fast, lightweight approach here since many of these views may be rendered).

我必须创建一个UIView具有圆角、边框、阴影的自定义,并且它的drawRect()方法被覆盖以提供自定义绘图代码,用这些代码将几条直线绘制到视图中(我需要在这里使用一种快速、轻量级的方法,因为很多可以呈现这些视图)。

The problem I'm currently facing is that the shadow doesn't apply anymore to the round corners as soon as I override drawRect()in the view class (even without any custom code yet in it). See the attached image for the difference:

我目前面临的问题是,一旦我drawRect()在视图类中覆盖(即使其中还没有任何自定义代码),阴影就不再适用于圆角。区别见附图:

enter image description here

在此处输入图片说明

In the view controller I'm using the following code:

在视图控制器中,我使用以下代码:

    view.layer.cornerRadius = 10;
    view.layer.masksToBounds = true;

    view.layer.borderColor = UIColor.grayColor().CGColor;
    view.layer.borderWidth = 0.5;

    view.layer.contentsScale = UIScreen.mainScreen().scale;
    view.layer.shadowColor = UIColor.blackColor().CGColor;
    view.layer.shadowOffset = CGSizeZero;
    view.layer.shadowRadius = 5.0;
    view.layer.shadowOpacity = 0.5;
    view.layer.masksToBounds = false;
    view.clipsToBounds = false;

In the overridden drawContext()I would use something like:

在覆盖中,drawContext()我会使用类似的东西:

    var context:CGContext = UIGraphicsGetCurrentContext();
    CGContextSetStrokeColorWithColor(context, UIColor.redColor().CGColor);
    // Draw them with a 2.0 stroke width so they are a bit more visible.
    CGContextSetLineWidth(context, 2.0);
    CGContextMoveToPoint(context, 0.0, 0.0); //start at this point
    CGContextAddLineToPoint(context, 20.0, 20.0); //draw to this point
    CGContextStrokePath(context);

But as said above, the shadow problem occurs even without this code added.

但是如上所述,即使不添加此代码,也会出现阴影问题。

Is there any other/better way to draw lightweight elements onto a view other than this approach that is compatible with round corners and shadows? I don't want to add any unnecessary extra views or image contexts to the view since these need to be light and performant.

除了与圆角和阴影兼容的这种方法之外,还有其他/更好的方法可以将轻量级元素绘制到视图上吗?我不想在视图中添加任何不必要的额外视图或图像上下文,因为它们需要轻量级和高性能。

回答by Mundi

This is a tricky one. UIView's clipsToBoundsis necessary to get the rounded corners. But CALayer's masksToBoundshas to be falseso the shadow is visible. Somehow, everything works if drawRectis not overridden, but actually it shouldn't.

这是一个棘手的问题。UIView'sclipsToBounds是获得圆角所必需的。但是CALayer'smasksToBounds必须是false这样阴影是可见的。不知何故,如果drawRect不被覆盖,一切都会正常,但实际上不应该。

The solution is to create a superview to provide the shadow (in the demonstration below this is the shadowView). You can test the following in Playground:

解决方案是创建一个超级视图来提供阴影(在下面的演示中,这是shadowView)。您可以在 Playground 中测试以下内容:

class MyView : UIView {
    override func drawRect(rect: CGRect) {
        let c = UIGraphicsGetCurrentContext()
        CGContextAddRect(c, CGRectMake(10, 10, 80, 80))
        CGContextSetStrokeColorWithColor(c , UIColor.redColor().CGColor)
        CGContextStrokePath(c)
    }
}

let superview = UIView(frame: CGRectMake(0, 0, 200, 200))

let shadowView = UIView(frame: CGRectMake(50, 50, 100, 100))
shadowView.layer.shadowColor = UIColor.blackColor().CGColor
shadowView.layer.shadowOffset = CGSizeZero
shadowView.layer.shadowOpacity = 0.5
shadowView.layer.shadowRadius = 5

let view = MyView(frame: shadowView.bounds)
view.backgroundColor = UIColor.whiteColor()
view.layer.cornerRadius = 10.0
view.layer.borderColor = UIColor.grayColor().CGColor
view.layer.borderWidth = 0.5
view.clipsToBounds = true

shadowView.addSubview(view)
superview.addSubview(shadowView)

Result:

结果:

enter image description here

在此处输入图片说明

回答by Frédéric Adda

I wrote a small extension to UIView to manage both rounded corners AND drop shadow. As the variables are @IBInspectable, everything can be set directly in the storyboard !

我为 UIView 编写了一个小的扩展来管理圆角和阴影。由于变量是@IBInspectable,所以一切都可以直接在故事板中设置!

//
//  UIView extensions.swift
//
//  Created by Frédéric ADDA on 25/07/2016.
//  Copyright ? 2016 Frédéric ADDA. All rights reserved.
//

import UIKit

extension UIView {

    @IBInspectable var shadow: Bool {
        get {
            return layer.shadowOpacity > 0.0
        }
        set {
            if newValue == true {
                self.addShadow()
            }
        }
    }

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

            // Don't touch the masksToBound property if a shadow is needed in addition to the cornerRadius
            if shadow == false {
                self.layer.masksToBounds = true
            }
        }
    }


    func addShadow(shadowColor: CGColor = UIColor.black.cgColor,
               shadowOffset: CGSize = CGSize(width: 1.0, height: 2.0),
               shadowOpacity: Float = 0.4,
               shadowRadius: CGFloat = 3.0) {
        layer.shadowColor = shadowColor
        layer.shadowOffset = shadowOffset
        layer.shadowOpacity = shadowOpacity
        layer.shadowRadius = shadowRadius
    }
}

And this is how it looks in the storyboard : storyboard

这就是它在故事板中的样子: 故事板

The result : enter image description here

结果 : 在此处输入图片说明

There is one requirement : DON'T touch either clipToBounds on the view (in code or in IB) or masksToBound on the layer.

有一个要求:不要触摸视图上的 clipToBounds(在代码中或在 IB 中)或图层上的 maskToBound。

NB: one case in which it won't work : tableViews. As UITableView automatically triggers clipToBoundsunder the hood, we can't have a drop shadow.

注意:一种不起作用的情况:tableViews。由于 UITableViewclipToBounds在幕后自动触发,我们不能有阴影。

EDIT: as Claudia Fitero aptly noticed, you need to leave a small padding around the view to which you are adding a shadow, otherwise the shadow won't be visible. A 2px-padding is enough generally (depending on your shadow radius).

编辑:正如 Claudia Fitero 恰当地注意到的那样,您需要在要添加阴影的视图周围留下一个小填充,否则阴影将不可见。通常 2px-padding 就足够了(取决于您的阴影半径)。

回答by Hodit

Shadow is dropped from whatever is inside view's layer. When you disable clipping, entire layer rectangle gets filled with default backgroundColorso the shadow becomes rectangular too. Instead of clipping it with rounded mask just make layer's contents rounded, draw them yourself. And layer's border is drawn around its bounds, so you need to draw it yourself too.

阴影从视图层内的任何内容中删除。当您禁用剪辑时,整个图层矩形将被默认填充,backgroundColor因此阴影也变成矩形。而不是用圆形蒙版剪裁它只是让图层的内容四舍五入,自己绘制它们。Andlayer的边框是围绕其边界绘制的,因此您也需要自己绘制它。

For example, in backgroundColorsetter set actual background color to clearColorand use passed color in drawRectto draw a rounded rect with.

例如,在backgroundColorsetter 中将实际背景颜色设置为clearColor并使用传递的颜色drawRect来绘制圆角矩形。

In example below I declare properties as IBInspectableand the whole class as IBDesignable, so everything can be set in storyboard. This way you can even use default Background selector to change your rounded rect color.

在下面的示例中,我将属性声明为IBInspectable,整个类声明为IBDesignable,因此所有内容都可以在故事板中设置。这样你甚至可以使用默认的背景选择器来改变你的圆形矩形颜色。

Swift

迅速

@IBDesignable class RoundRectView: UIView {

    @IBInspectable var cornerRadius: CGFloat = 0.0
    @IBInspectable var borderColor: UIColor = UIColor.blackColor()
    @IBInspectable var borderWidth: CGFloat = 0.5
    private var customBackgroundColor = UIColor.whiteColor()
    override var backgroundColor: UIColor?{
        didSet {
            customBackgroundColor = backgroundColor!
            super.backgroundColor = UIColor.clearColor()
        }
    }

    func setup() {
        layer.shadowColor = UIColor.blackColor().CGColor;
        layer.shadowOffset = CGSizeZero;
        layer.shadowRadius = 5.0;
        layer.shadowOpacity = 0.5;
        super.backgroundColor = UIColor.clearColor()
    }

    override init(frame: CGRect) {
        super.init(frame: frame)
        self.setup()
    }

    required init?(coder aDecoder: NSCoder) {
        super.init(coder: aDecoder)
        self.setup()
    }

    override func drawRect(rect: CGRect) {
        customBackgroundColor.setFill()
        UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill()

        let borderRect = CGRectInset(bounds, borderWidth/2, borderWidth/2)
        let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2)
        borderColor.setStroke()
        borderPath.lineWidth = borderWidth
        borderPath.stroke()

        // whatever else you need drawn
    }
}

Swift 3

斯威夫特 3

@IBDesignable class RoundedView: UIView {

@IBInspectable var cornerRadius: CGFloat = 0.0
@IBInspectable var borderColor: UIColor = UIColor.black
@IBInspectable var borderWidth: CGFloat = 0.5
private var customBackgroundColor = UIColor.white
override var backgroundColor: UIColor?{
    didSet {
        customBackgroundColor = backgroundColor!
        super.backgroundColor = UIColor.clear
    }
}

func setup() {
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize.zero
    layer.shadowRadius = 5.0
    layer.shadowOpacity = 0.5
    super.backgroundColor = UIColor.clear
}

override init(frame: CGRect) {
    super.init(frame: frame)
    self.setup()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.setup()
}

override func draw(_ rect: CGRect) {
    customBackgroundColor.setFill()
    UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill()

    let borderRect = bounds.insetBy(dx: borderWidth/2, dy: borderWidth/2)
    let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2)
    borderColor.setStroke()
    borderPath.lineWidth = borderWidth
    borderPath.stroke()

    // whatever else you need drawn
}
}

Objective-C .h

目标-C .h

IB_DESIGNABLE
@interface RoundRectView : UIView
@property IBInspectable CGFloat cornerRadius;
@property IBInspectable UIColor *borderColor;
@property IBInspectable CGFloat borderWidth;
@end

Objective-C .m

目标-C .m

@interface RoundRectView()
@property UIColor *customBackgroundColor;
@end

@implementation RoundRectView

-(void)setup{
    self.layer.shadowColor = [UIColor blackColor].CGColor;
    self.layer.shadowOffset = CGSizeZero;
    self.layer.shadowRadius = 5.0;
    self.layer.shadowOpacity = 0.5;
    [super setBackgroundColor:[UIColor clearColor]];
}

- (instancetype)initWithFrame:(CGRect)frame
{
    self = [super initWithFrame:frame];
    if (self) {
        [self setup];
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)coder
{
    self = [super initWithCoder:coder];
    if (self) {
        [self setup];
    }
    return self;
}

-(void)setBackgroundColor:(UIColor *)backgroundColor{
    self.customBackgroundColor = backgroundColor;
    super.backgroundColor = [UIColor clearColor];
}

-(void)drawRect:(CGRect)rect{
    [self.customBackgroundColor setFill];
    [[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:self.cornerRadius] fill];

    CGFloat borderInset = self.borderWidth/2;
    CGRect borderRect = CGRectInset(self.bounds, borderInset, borderInset);
    UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:borderRect cornerRadius:self.cornerRadius - borderInset];
    [self.borderColor setStroke];
    borderPath.lineWidth = self.borderWidth;
    [borderPath stroke];

    // whatever else you need drawn
}

@end

Result

结果

回答by amagain

Here's the swift3 version of Hodit'sanswer, I had to use it and found it over here and did general corrections for XCode 8. Works like charm!

这是Hodit答案的 swift3 版本,我不得不使用它并在此处找到它,并对 XCode 8 进行了一般更正。就像魅力一样!

@IBDesignable class RoundRectView: UIView {

@IBInspectable var cornerRadius: CGFloat = 0.0
@IBInspectable var borderColor: UIColor = UIColor.black
@IBInspectable var borderWidth: CGFloat = 0.5
private var customBackgroundColor = UIColor.white
override var backgroundColor: UIColor?{
    didSet {
        customBackgroundColor = backgroundColor!
        super.backgroundColor = UIColor.clear
    }
}

func setup() {
    layer.shadowColor = UIColor.black.cgColor;
    layer.shadowOffset = CGSize.zero
    layer.shadowRadius = 5.0;
    layer.shadowOpacity = 0.5;
    super.backgroundColor = UIColor.clear
}

override init(frame: CGRect) {
    super.init(frame: frame)
    self.setup()
}

required init?(coder aDecoder: NSCoder) {
    super.init(coder: aDecoder)
    self.setup()
}

override func draw(_ rect: CGRect) {
    customBackgroundColor.setFill()
    UIBezierPath(roundedRect: bounds, cornerRadius: cornerRadius ?? 0).fill()

    let borderRect = bounds.insetBy(dx: borderWidth/2, dy: borderWidth/2)
    let borderPath = UIBezierPath(roundedRect: borderRect, cornerRadius: cornerRadius - borderWidth/2)
    borderColor.setStroke()
    borderPath.lineWidth = borderWidth
    borderPath.stroke()

    // whatever else you need drawn
}
}

回答by Zulqarnain

I find the following link helpful to understand setting the dropshadow:

我发现以下链接有助于理解设置阴影:

How to add a shadow to a UIView

如何为 UIView 添加阴影

To set the round corner for UIVIEW just set the layer.cornerRadiusvalue in interface builder, Please check screenshot. enter image description here

要为 UIVIEW 设置圆角,只需layer.cornerRadius在界面构建器中设置值,请查看屏幕截图。 在此处输入图片说明

回答by xlsmearlx

Swift 3

斯威夫特 3

I made an UIView extension and its basically the same idea suggested by Mundi:

我做了一个 UIView 扩展,它与 Mundi 建议的想法基本相同:

extension UIView {

func addShadowView() {
    //Remove previous shadow views
    superview?.viewWithTag(119900)?.removeFromSuperview()

    //Create new shadow view with frame
    let shadowView = UIView(frame: frame)
    shadowView.tag = 119900
    shadowView.layer.shadowColor = UIColor.black.cgColor
    shadowView.layer.shadowOffset = CGSize(width: 2, height: 3)
    shadowView.layer.masksToBounds = false

    shadowView.layer.shadowOpacity = 0.3
    shadowView.layer.shadowRadius = 3
    shadowView.layer.shadowPath = UIBezierPath(rect: bounds).cgPath
    shadowView.layer.rasterizationScale = UIScreen.main.scale
    shadowView.layer.shouldRasterize = true

    superview?.insertSubview(shadowView, belowSubview: self)
}}

Use:

用:

class MyCVCell: UICollectionViewCell {

@IBOutlet weak var containerView: UIView!

override func awakeFromNib() {
    super.awakeFromNib()
}

override func draw(_ rect: CGRect) {
    super.draw(rect)
    containerView.addShadowView()
}}

Result

结果

回答by Oscar

SWIFT 3 Solution

SWIFT 3 解决方案

Adapted from Mundi's answer

改编自 Mundi 的回答

class MyView : UIView {
        override func draw(_ rect: CGRect) {
            let c = UIGraphicsGetCurrentContext()
            c!.addRect(CGRect(x: 10, y: 10, width: 80, height: 80))
            c!.setStrokeColor(UIColor.red.cgColor)
            c!.strokePath()
        }
    }

let superview = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 200))

let shadowView = UIView(frame: CGRect(x: 50, y: 50, width: 100, height: 100))
shadowView.layer.shadowColor = UIColor.black.cgColor
shadowView.layer.shadowOffset = CGSize.zero
shadowView.layer.shadowOpacity = 0.5
shadowView.layer.shadowRadius = 5

let view = MyView(frame: shadowView.bounds)
view.backgroundColor = UIColor.white
view.layer.cornerRadius = 10.0
view.layer.borderColor = UIColor.gray.cgColor
view.layer.borderWidth = 0.5
view.clipsToBounds = true

shadowView.addSubview(view)
superview.addSubview(shadowView)

回答by ar34z

The solution seems much easier than the problem might suggest. I had this with one of my views and used the core part of @Hodit's answer to get it to work. This is all you need actually:

解决方案似乎比问题所暗示的要容易得多。我有我的一个观点,并使用@Hodit 答案的核心部分来让它工作。这就是你真正需要的:

- (void) drawRect:(CGRect)rect {
    // make sure the background is set to a transparent color using IB or code
    // e.g.: self.backgroundColor = [UIColor clearColor]; 

    // draw a rounded rect in the view
    [[UIColor whiteColor] setFill];
    [[UIBezierPath bezierPathWithRoundedRect:self.bounds cornerRadius:5.0] fill];

    // apply shadow if you haven't already
    self.layer.masksToBounds = NO;
    self.layer.shadowColor = [[UIColor blackColor] CGColor];
    self.layer.shadowOffset = CGSizeMake(0.0,3.0);
    self.layer.shadowRadius= 1.0;
    self.layer.shadowOpacity = 0.1;

    // more code here

}

Note that this doesn't clip subviews. Anything positioned at 0,0 in the view will overlap the visible top left rounded corner.

请注意,这不会剪辑子视图。视图中位于 0,0 处的任何内容都将与可见的左上角圆角重叠。

回答by Piyush Sanepara

Try this it is work for me...

试试这个它对我有用......

    yourView.layer.shadowColor = UIColor.blackColor().CGColor
    yourView.layer.shadowOpacity = 0.5
    yourView.layer.shadowOffset = CGSize(width: 3, height: 3)
    yourView.layer.shadowRadius = 05

    yourView.layer.shadowPath = UIBezierPath(rect: yourView.bounds).CGPath
    yourView.layer.shouldRasterize = true

回答by Maria Ortega

In Swift. What did work for me was adding:

在斯威夫特。对我有用的是添加:

    self.noteImage.layer.masksToBounds = false

So, the full code is:

所以,完整的代码是:

    self.noteImage.layer.masksToBounds = false
    self.noteImage.layer.shadowColor = UIColor.redColor().CGColor
    self.noteImage.layer.shadowOpacity = 0.5
    self.noteImage.layer.shadowOffset = CGSize(width: 2, height: 2)
    self.noteImage.layer.shadowRadius = 1

    self.noteImage.layer.shadowPath = UIBezierPath(rect: noteImage.bounds).CGPath
    self.noteImage.layer.shouldRasterize = true