web-dev-qa-db-ja.com

影付きのUIView、丸い角、カスタムdrawRect

丸い角、境界線、影を持つカスタムUIViewを作成し、そのdrawRect()メソッドをオーバーライドして、いくつかの直線をビューに描画するカスタム描画コードを提供する必要があります(必要です)これらのビューの多くがレンダリングされる可能性があるため、ここで高速で軽量なアプローチを使用します)。

現在直面している問題は、ビュークラスでdrawRect()をオーバーライドするとすぐに(まだカスタムコードがなくても)影が丸い角に適用されないことです。違いについては添付の画像をご覧ください。

enter image description here

View Controllerでは、次のコードを使用しています。

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

しかし、上記のように、このコードを追加しなくてもシャドウの問題が発生します。

丸い角や影と互換性があるこのアプローチ以外に、軽量要素をビューに描画する他の/より良い方法はありますか?不要で余分なビューや画像コンテキストをビューに追加したくないのは、これらが軽量で高性能である必要があるためです。

49
BadmintonCat

これはトリッキーなものです。 UIViewclipsToBoundsは、角を丸くするために必要です。ただし、CALayermasksToBoundsfalseでなければならないため、影が見えるようになります。どういうわけか、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)

結果:

enter image description here

90
Mundi

丸い角とドロップシャドウの両方を管理するために、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
    }
}

そして、これがストーリーボードでどのように見えるかです: storyboard

結果 : - enter image description here

1つの要件があります:ビューのclipToBounds(コードまたはIB)またはレイヤーのmasksToBoundに触れないでください。

NB:それが動作しない1つのケース:tableViews。 UITableViewは内部でclipToBoundsを自動的にトリガーするため、ドロップシャドウを設定することはできません。

編集:Claudia Fiteroが適切に気づいたように、影を追加するビューの周りに小さなパディングを残す必要があります。そうしないと、影は見えません。通常、2pxのパディングで十分です(シャドウの半径によって異なります)。

41
Frédéric Adda

ビューのレイヤー内にあるものから影が落とされます。クリッピングを無効にすると、レイヤーの長方形全体がデフォルトのbackgroundColorで塗りつぶされるため、シャドウも長方形になります。丸いマスクでクリップする代わりに、レイヤーのコンテンツを丸くして、自分で描画します。また、layerの境界線は境界の周囲に描画されるため、自分で境界線を描画する必要があります。

たとえば、backgroundColor setterでは、実際の背景色をclearColorに設定し、drawRectで渡された色を使用して、丸みのある長方形を描画します。

以下の例では、プロパティを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
    }
}

スイフト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

結果

18
Hodit

これが Hodit's の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
}
}
8
amagain

スイフト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()
}}

Result

5
xlsmearlx

次のリンクは、ドロップシャドウの設定を理解するのに役立ちます。

IViewに影を追加する方法

UIVIEWのラウンドコーナーを設定するには、インターフェイスビルダーでlayer.cornerRadius値を設定するだけです。スクリーンショットを確認してください。 enter image description here

4
Zulqarnain

Swift 3ソリューション

ムンディの答えから改作

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
Oscar

解決策は、問題が示唆するよりもはるかに簡単に思えます。私は自分の意見の1つでこれを持ち、@ 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に配置されたものはすべて、目に見える左上の丸い角と重なります。

2
ar34z

これが私の解決策です。 UIView、UIControl、UITableViewなどの複数のタイプのビューがあり、それぞれのサブクラスを作成したくない場合、またはコードにわずかな変更を加えてこの効果を追加したい場合は、これが探しています。

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
1
arthurdai

フレデリックアダのソリューションに加えて、影を描画できるスーパービューにパディングで影のあるビューを配置することを忘れないでください。そうしないと、影が切り取られます。カスタムセルでこの間違いを犯し、8pxのパディングを追加するまで解決策が間違っていると思いました。

1
Claudia Fitero

Swiftで。私のために働いたのは追加することでした:

    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
1
Maria Ortega

これを試してみてください、それは私の仕事です...

    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
1
Piyush Sanepara

Swift 4.1。 UIViewの角を丸くするために、次のようにUIViewのExtensionを作成しました。

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

enter image description hereenter image description here

0
GSK

この拡張機能を使用して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
        }
    }
}
0
MBH

これは古い質問ですが、カスタム描画メソッドで次のようにすべてを行っただけです。

丸みのあるビューにドロップシャドウを適用することがわかっている場合は、通常これを行います(もちろん、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()
}
}
0
WBuck