UIView with shadow, rounded corners and custom drawRect

前端 未结 16 1281
南旧
南旧 2020-12-02 05:22

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

相关标签:
16条回答
  • 2020-12-02 05:46

    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

    0 讨论(0)
  • 2020-12-02 05:47

    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
    
    0 讨论(0)
  • 2020-12-02 05:48

    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
    
    0 讨论(0)
  • 2020-12-02 05:48

    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
    
    0 讨论(0)
  • 2020-12-02 05:48

    In Swift 4.1. For making rounded corner of UIView I have created Extension of UIView as follow.

    import UIKit
    
    class ViewController: UIViewController {
    
        @IBOutlet weak var viewOuter: UIView!
        @IBOutlet weak var viewInner: UIView!
    
        override func viewDidLoad() {
            super.viewDidLoad()
            viewOuter.backgroundColor = UIColor.clear
            viewInner.roundCorners(15.0)
            viewOuter.addViewShadow()
        }
    }
    extension UIView {
        public func roundCorners(_ cornerRadius: CGFloat) {
            self.layer.cornerRadius = cornerRadius
            self.clipsToBounds = true
            self.layer.masksToBounds = true
        }
    
        public func addViewShadow() {
            DispatchQueue.main.asyncAfter(deadline: (.now() + 0.2)) {
                let shadowLayer = CAShapeLayer()
                shadowLayer.path = UIBezierPath(roundedRect: self.bounds, cornerRadius: 15).cgPath
                shadowLayer.fillColor = UIColor.white.cgColor
    
                shadowLayer.shadowColor = UIColor.lightGray.cgColor
                shadowLayer.shadowPath = shadowLayer.path
                shadowLayer.shadowOffset = CGSize(width: 2.6, height: 2.6)
                shadowLayer.shadowOpacity = 0.8
                shadowLayer.shadowRadius = 8.0
                self.layer.insertSublayer(shadowLayer, at: 0)
            }
        }
    }
    

    0 讨论(0)
  • 2020-12-02 05:49

    This is an older question, but I would have just done everything in your custom draw method like below.

    I usually will do this if I know I want to apply a drop shadow to my rounded view (which of course means I don't want to use masksToBounds)

    You also don't have to add an extra "shadow view" to the hierarchy.

    @IBDesignable
    class RoundedView: UIView {
    
    @IBInspectable
    var cornerRadius: CGFloat = 0
    
    override func draw(_ rect: CGRect) {
        guard let context = UIGraphicsGetCurrentContext() else { return }
        // You could use custom IBInspectable attributes
        // for the stroke and fill color.
        context.setFillColor(UIColor.white.cgColor)
        context.setStrokeColor(UIColor.orange.cgColor)
        // Add a clipping path to get the rounded look
        // you want.
        UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius).addClip()
        // Fill and stroke your background.
        let background = UIBezierPath(roundedRect: rect, cornerRadius: cornerRadius)
        background.lineWidth = 2
        background.fill()
        background.stroke()
    }
    
    private func shadow() {
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowRadius = 5
        layer.shadowOpacity = 0.5
        layer.shadowOffset = CGSize.zero
    }
    
    override func awakeFromNib() {
        super.awakeFromNib()
        shadow()
    }
    }
    
    0 讨论(0)
提交回复
热议问题