簡體   English   中英

帶有陰影、圓角和自定義 drawRect 的 UIView

[英]UIView with shadow, rounded corners and custom drawRect

我必須創建一個具有圓角、邊框、陰影的自定義UIView ,並且它的drawRect()方法被覆蓋以提供自定義繪圖代碼,通過這些代碼將幾條直線繪制到視圖中(我需要使用快速、輕量級的方法在這里,因為許多這些視圖可能會被渲染)。

我目前面臨的問題是,一旦我在視圖類中覆蓋drawRect() ,陰影就不再適用於圓角(即使其中還沒有任何自定義代碼)。 區別見附圖:

在此處輸入圖片說明

在視圖控制器中,我使用以下代碼:

    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;

在覆蓋的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);

但是如上所述,即使不添加此代碼,也會出現陰影問題。

除了與圓角和陰影兼容的這種方法之外,還有其他/更好的方法可以將輕量級元素繪制到視圖上嗎? 我不想在視圖中添加任何不必要的額外視圖或圖像上下文,因為它們需要輕量級和高性能。

這是一個棘手的問題。 UIViewclipsToBounds是獲得圓角所必需的。 但是CALayermasksToBounds必須是false所以陰影是可見的。 不知何故,如果drawRect沒有被覆蓋,則一切正常,但實際上它不應該被覆蓋。

解決方案是創建一個超級視圖來提供陰影(在下面的演示中,這是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)

結果:

在此處輸入圖片說明

我為 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
    }
}

這就是它在故事板中的樣子: 故事板

結果 : 在此處輸入圖片說明

有一個要求:不要觸摸視圖上的 clipToBounds(在代碼中或在 IB 中)或圖層上的 maskToBound。

注意:一種不起作用的情況:tableViews。 由於 UITableView 在clipToBounds自動觸發clipToBounds ,我們不能有陰影。

編輯:正如 Claudia Fitero 恰當地注意到的那樣,您需要在要添加陰影的視圖周圍留下一個小填充,否則陰影將不可見。 通常 2px-padding 就足夠了(取決於您的陰影半徑)。

陰影從視圖層內的任何內容中刪除。 當您禁用剪輯時,整個圖層矩形將填充為默認backgroundColor因此陰影也變為矩形。 而不是用圓形蒙版剪裁它只是讓圖層的內容四舍五入,自己繪制它們。 而且layer的邊框是圍繞它的邊界繪制的,所以你也需要自己繪制它。

例如,在backgroundColor setter 中將實際背景顏色設置為clearColor並使用drawRect傳遞的顏色來繪制圓角矩形。

在下面的示例中,我將屬性聲明為IBInspectable並將整個類聲明為IBDesignable ,因此所有內容都可以在故事板中設置。 這樣你甚至可以使用默認的背景選擇器來改變你的圓形矩形顏色。

迅速

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

斯威夫特 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
}
}

目標-C .h

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

目標-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

結果

這是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
}
}

我發現以下鏈接有助於理解設置陰影:

如何為 UIView 添加陰影

要為 UIVIEW 設置圓角,只需在界面構建器中設置layer.cornerRadius值,請查看屏幕截圖。 在此處輸入圖片說明

SWIFT 3 解決方案

改編自 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)

斯威夫特 3

我做了一個 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)
}}

用:

class MyCVCell: UICollectionViewCell {

@IBOutlet weak var containerView: UIView!

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

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

結果

在 Swift 4.1 中。 為了制作 UIView 的圓角,我創建了 UIView 的擴展如下。

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

在此處輸入圖片說明 在此處輸入圖片說明

解決方案似乎比問題所暗示的要容易得多。 我有我的一個觀點,並使用@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

}

請注意,這不會剪輯子視圖。 視圖中位於 0,0 處的任何內容都將與可見的左上角圓角重疊。

我將此擴展用於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 bcolor = layer.borderColor else {
                return nil
            }
            return UIColor(cgColor: bcolor)
        }
        set(value) {
            layer.borderColor = value?.cgColor
        }
    }
}

試試這個它對我有用......

    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

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

    self.noteImage.layer.masksToBounds = false

所以,完整的代碼是:

    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

除了 Frederic Adda 的解決方案之外,不要忘記將具有陰影的視圖定位到可以繪制陰影的超級視圖。 否則陰影將被剪掉。 我在自定義單元格中犯了這個錯誤,並認為解決方案是錯誤的,直到我在四周添加了 8px 的填充。

這是我的解決方案。 如果您有多種類型的視圖,例如 UIView、UIControl、UITableView 等,並且不想為它們中的每一個創建子類,或者您想以最小的代碼更改添加此效果,那么這可能是您的選擇正在找。

目標通道

#import <UIKit/UIKit.h>

@interface UIView (CornerAndShadow)

- (void)setCornerAndShadow;

@end

目標-厘米

#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

這是一個較舊的問題,但我會在您的自定義繪制方法中完成所有操作,如下所示。

如果我知道我想對圓形視圖應用陰影,我通常會這樣做(這當然意味着我不想使用masksToBounds

您也不必向層次結構添加額外的“陰影視圖”。

@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()
}
}

您可以將此功能用於所有視圖。

    extension UIView{

    func radiusAndBorder(radius:CGFloat, color:UIColor = UIColor.clear) -> UIView{
        var rounfView:UIView = self
        rounfView.layer.cornerRadius = CGFloat(radius)
        rounfView.layer.borderWidth = 1
        rounfView.layer.borderColor = color.cgColor
        rounfView.clipsToBounds = true
        return rounfView
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM