UIView with shadow, rounded corners and custom dra

2019-01-12 18:13发布

I have to create a custom UIView that 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).

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:

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:

    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.

16条回答
何必那么认真
2楼-- · 2019-01-12 18:22

Swift 3

I made an UIView extension and its basically the same idea suggested by 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

查看更多
Deceive 欺骗
3楼-- · 2019-01-12 18:23

In addition to the Frederic Adda's solution, don't forget to position the view that has shadow with a padding to the superview, where the shadow can be drawn. Otherwise the shadow will be clipped off. I made this mistake in my custom cell, and thought the solution was wrong until I added a padding of 8px all around.

查看更多
闹够了就滚
4楼-- · 2019-01-12 18:24

I use this extension to UIView:

Import UIKit

extension UIView {

    /// A property that accesses the backing layer's opacity.
    @IBInspectable
    open var opacity: Float {
        get {
            return layer.opacity
        }
        set(value) {
            layer.opacity = value
        }
    }

    /// A property that accesses the backing layer's shadow
    @IBInspectable
    open var shadowColor: UIColor? {
        get {
            guard let v = layer.shadowColor else {
                return nil
            }

            return UIColor(cgColor: v)
        }
        set(value) {
            layer.shadowColor = value?.cgColor
        }
    }

    /// A property that accesses the backing layer's shadowOffset.
    @IBInspectable
    open var shadowOffset: CGSize {
        get {
            return layer.shadowOffset
        }
        set(value) {
            layer.shadowOffset = value
        }
    }

    /// A property that accesses the backing layer's shadowOpacity.
    @IBInspectable
    open var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set(value) {
            layer.shadowOpacity = value
        }
    }

    /// A property that accesses the backing layer's shadowRadius.
    @IBInspectable
    open var shadowRadius: CGFloat {
        get {
            return layer.shadowRadius
        }
        set(value) {
            layer.shadowRadius = value
        }
    }

    /// A property that accesses the backing layer's shadowPath.
    @IBInspectable
    open var shadowPath: CGPath? {
        get {
            return layer.shadowPath
        }
        set(value) {
            layer.shadowPath = value
        }
    }


    /// A property that accesses the layer.cornerRadius.
    @IBInspectable
    open var cornerRadius: CGFloat {
        get {
            return layer.cornerRadius
        }
        set(value) {
            layer.cornerRadius = value
        }
    }


    /// A property that accesses the layer.borderWith.
    @IBInspectable
    open var borderWidth: CGFloat {
        get {
            return layer.borderWidth
        }
        set(value) {
            layer.borderWidth = value
        }
    }

    /// A property that accesses the layer.borderColor property.
    @IBInspectable
    open var borderColor: UIColor? {
        get {
            guard let v = layer.borderColor else {
                return nil
            }
            return UIColor(cgColor: v)
        }
        set(value) {
            layer.borderColor = value?.cgColor
        }
    }
}
查看更多
beautiful°
5楼-- · 2019-01-12 18:25

Shadow is dropped from whatever is inside view's layer. When you disable clipping, entire layer rectangle gets filled with default backgroundColor so 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.

For example, in backgroundColor setter set actual background color to clearColor and use passed color in drawRect to draw a rounded rect with.

In example below I declare properties as IBInspectable and 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.

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

@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

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

Objective-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

查看更多
萌系小妹纸
6楼-- · 2019-01-12 18:30

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
查看更多
男人必须洒脱
7楼-- · 2019-01-12 18:30

This is my solution. If you have multiple type of views like UIView, UIControl, UITableView and so on, and don't want to make subclasses of each of them, or you want to add this effect with smallest changes to your code, then this might be what you are looking for.

Objective-C.h

#import <UIKit/UIKit.h>

@interface UIView (CornerAndShadow)

- (void)setCornerAndShadow;

@end

Objective-C.m

#import "UIView+CornerAndShadow.h"
#import <Masonry.h>

@implementation UIView (CornerAndShadow)

- (void)setCornerAndShadow {
    // constants
    CGFloat fCornerRadius = 9.f;

    // only work for views with superview
    if (self.superview == nil) {
        return;
    }

    // set corner
    self.layer.cornerRadius = fCornerRadius;
    self.layer.masksToBounds = YES;

    // create and configure shadowView
    UIView *shadowView = [UIView new];
    shadowView.backgroundColor = self.backgroundColor; // just to make shadow visible
    shadowView.layer.cornerRadius = fCornerRadius;
    shadowView.layer.shadowColor = [UIColor redColor].CGColor;
    shadowView.layer.shadowOffset = CGSizeMake(0, 3.f);
    shadowView.layer.shadowOpacity = 0.5f;
    shadowView.layer.shadowRadius = 5.f;

    // put shadowView into superview right below self
    [self.superview insertSubview:shadowView belowSubview:self];

    // set shadowView's frame equal to self
    [shadowView mas_makeConstraints:^(MASConstraintMaker *make) {
        make.edges.equalTo(self);
    }];
    // use this if you're not using autolayout, and can get real frame here
    // shadowView.frame = self.frame;
}

@end
查看更多
登录 后发表回答