ฉันจะวาดเงาภายใต้ UIView ได้อย่างไร


352

ฉันพยายามวาดเงาใต้ขอบล่างของ a UIViewใน Cocoa Touch ฉันเข้าใจว่าฉันควรใช้CGContextSetShadow()เพื่อวาดเงา แต่คู่มือการเขียนโปรแกรม Quartz 2D นั้นค่อนข้างคลุมเครือ:

  1. บันทึกสถานะกราฟิก
  2. เรียกใช้ฟังก์ชันCGContextSetShadowส่งผ่านค่าที่เหมาะสม
  3. ดำเนินการวาดภาพทั้งหมดที่คุณต้องการใช้เงา
  4. กู้คืนสถานะกราฟิก

ฉันได้ลองทำสิ่งต่อไปนี้ในUIViewคลาสย่อย:

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    CGContextRestoreGState(currentContext);
    [super drawRect: rect];
}

.. แต่สิ่งนี้ไม่ได้ผลสำหรับฉันและฉันก็ติดอยู่กับ (ก) จะไปที่ไหนต่อไปและ (ข) หากมีสิ่งใดที่ฉันต้องทำกับฉันUIViewเพื่อทำงานนี้

คำตอบ:


98

ในรหัสปัจจุบันของคุณคุณบันทึกGStateบริบทปัจจุบันกำหนดค่าให้วาดเงา .. และกู้คืนไปยังสิ่งที่มันเป็นก่อนที่คุณจะกำหนดค่าให้วาดเงา จากนั้นในที่สุดคุณเรียกใช้การดำเนินการ superclass ของdrawRect:

การวาดใด ๆ ที่ควรได้รับผลกระทบจากการตั้งค่าเงาต้องเกิดขึ้นหลังจากนั้น

CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);

แต่ก่อน

CGContextRestoreGState(currentContext);

ดังนั้นถ้าคุณต้องการให้ซูเปอร์คลาสนั้นdrawRect:'ห่อ' ในเงาแล้วถ้าคุณจัดเรียงโค้ดของคุณใหม่แบบนี้ล่ะ

- (void)drawRect:(CGRect)rect {
    CGContextRef currentContext = UIGraphicsGetCurrentContext();
    CGContextSaveGState(currentContext);
    CGContextSetShadow(currentContext, CGSizeMake(-15, 20), 5);
    [super drawRect: rect];
    CGContextRestoreGState(currentContext);
}

789

วิธีที่ง่ายกว่ามากคือการตั้งค่าคุณลักษณะเลเยอร์บางอย่างของมุมมองเกี่ยวกับการเริ่มต้น:

self.layer.masksToBounds = NO;
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

คุณต้องนำเข้า QuartzCore

#import <QuartzCore/QuartzCore.h>

4
แต่โปรดทราบว่านี่ใช้งานได้กับ iOS 3.2 ขึ้นไปเท่านั้นดังนั้นหากแอปของคุณควรทำงานในเวอร์ชั่นเก่าคุณต้องใช้วิธีการแก้ปัญหาของคริสเตียนหรือภาพคงที่ด้านหลังมุมมองหากนี่เป็นตัวเลือก
florianbuerger

119
โซลูชันนี้ยังต้องการเพิ่ม#import <QuartzCore/QuartzCore.h>"ลงในไฟล์. h
MusiGenesis

26
การตั้งค่าmasksToBoundsที่NOจะลบล้างcornerRadiusไม่มีหรือไม่
pixelfreak

4
ตกลงเพื่อแก้ปัญหานั้น backgroundColor จำเป็นต้องตั้งค่าบนเลเยอร์และมุมมองจะต้องโปร่งใส
pixelfreak

@pixelfreak คุณทำเช่นนั้นได้อย่างไร ฉันพยายามself.layer.backgroundColor = [[UIColor whiteColor] CGColor];แต่ไม่มีโชค มุมมองใดต้องโปร่งใส
Victor Van Hee

232
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

นี่จะทำให้แอปพลิเคชันช้าลง การเพิ่มบรรทัดต่อไปนี้สามารถปรับปรุงประสิทธิภาพตราบใดที่มุมมองของคุณเป็นรูปสี่เหลี่ยมผืนผ้าอย่างชัดเจน:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;

1
เป็นไปได้ว่าการใช้การเพิ่มประสิทธิภาพนี้มีประโยชน์ก็ต่อเมื่อมุมมองของคุณเป็นรูปสี่เหลี่ยมผืนผ้าอย่างชัดเจน
Benjamin Dobell

1
self.layer.shadowPath ... แทนที่จะเป็นอะไร หรือเพียงแค่การเพิ่มมัน
Chrizzz

2
เพียงเพิ่มบรรทัดพิเศษนั้นนอกเหนือจากรายการอื่น ๆ
christophercotton

11
@NathanGaskin - การวาดเงาเป็นการดำเนินการที่มีราคาแพงดังนั้นตัวอย่างเช่นหากแอปพลิเคชันของคุณอนุญาตให้มีการวางแนวอินเทอร์เฟซอื่นและคุณเริ่มหมุนอุปกรณ์โดยไม่ต้องระบุเส้นทางของเงาอย่างชัดแจ้ง รูปร่างอาจจะทำให้ภาพเคลื่อนไหวช้าลงอย่างเห็นได้ชัด
Peter Pajchl

9
@BenjaminDobell บรรทัดนั้นใช้ได้เฉพาะกับรูปสี่เหลี่ยมผืนผ้าเท่านั้น แต่คุณสามารถสร้างเส้นทางที่ไม่ใช่รูปสี่เหลี่ยมผืนผ้าได้ ตัวอย่างเช่นถ้าคุณมีรูปสี่เหลี่ยมผืนผ้ามนคุณสามารถใช้ bezierPathWithRoundedRect: cornerRadius:
Dan Dyer

160

วิธีการแก้ปัญหาเดียวกัน แต่เพียงเพื่อเตือนคุณ: คุณสามารถกำหนดเงาโดยตรงในกระดานเรื่องราว

Ex:

ป้อนคำอธิบายรูปภาพที่นี่


2
น่าเศร้าที่ฉันคิดว่า CGColor ไม่สามารถเข้าถึงได้จากกระดานเรื่องราว :(
Antzi

1
เพียงแค่กำหนดหมวดหมู่สำหรับUIViewหรือCGLayerซึ่งเป็นUIColorเสื้อคลุมสำหรับCGColorคุณสมบัติ;)
DeFrenZ

1
ลิงก์นี้อธิบายถึงวิธีการทำเช่นนี้: cocoadventures.org/post/104137872754/…
Kamchatka

8
เพื่อให้ผู้คนสามารถคัดลอกและวางได้ง่ายขึ้น: layer.masksToBound, layer.shadowOffset, layer.shadowRadius, layer.shadowOpacity Typos จะฆ่าคุณที่นี่
etayluz

3
ค่า layer.shadowOpacity ควรเป็น 0.5 ไม่ใช่ 0,5
Desert Rose

43

คุณสามารถลองสิ่งนี้ .... คุณสามารถเล่นกับค่าต่างๆ shadowRadiusสั่งปริมาณของเบลอ shadowOffsetบอกตำแหน่งของเงา

Swift 2.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.CGPath

Swift 3.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height 
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height)) 
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.cgPath

ตัวอย่างด้วยสเปรด

ตัวอย่างด้วยสเปรด

เพื่อสร้างเงาพื้นฐาน

    demoView.layer.cornerRadius = 2
    demoView.layer.shadowColor = UIColor.blackColor().CGColor
    demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
    demoView.layer.shadowOpacity = 0.5 
    demoView.layer.shadowRadius = 5.0 //Here your control your blur

ตัวอย่าง Shadow พื้นฐานใน Swift 2.0

เอาท์พุท


19

วิธีที่ง่ายและสะอาดโดยใช้ตัวสร้างส่วนต่อประสาน

เพิ่มไฟล์ชื่อ UIView.swift ในโครงการของคุณ (หรือวางลงในไฟล์ใดก็ได้):

import UIKit

@IBDesignable extension UIView {

    /* The color of the shadow. Defaults to opaque black. Colors created
    * from patterns are currently NOT supported. Animatable. */
    @IBInspectable var shadowColor: UIColor? {
        set {
            layer.shadowColor = newValue!.CGColor
        }
        get {
            if let color = layer.shadowColor {
                return UIColor(CGColor:color)
            }
            else {
                return nil
            }
        }
    }

    /* The opacity of the shadow. Defaults to 0. Specifying a value outside the
    * [0,1] range will give undefined results. Animatable. */
    @IBInspectable var shadowOpacity: Float {
        set {
            layer.shadowOpacity = newValue
        }
        get {
            return layer.shadowOpacity
        }
    }

    /* The shadow offset. Defaults to (0, -3). Animatable. */
    @IBInspectable var shadowOffset: CGPoint {
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
    }

    /* The blur radius used to create the shadow. Defaults to 3. Animatable. */
    @IBInspectable var shadowRadius: CGFloat {
        set {
            layer.shadowRadius = newValue
        }
        get {
            return layer.shadowRadius
        }
    }
}

จากนั้นจะพร้อมใช้งานในตัวสร้างส่วนติดต่อสำหรับทุกมุมมองในแผงเครื่องมืออรรถประโยชน์> ตัวตรวจสอบแอตทริบิวต์:

แผงยูทิลิตี้

คุณสามารถตั้งค่าเงาได้อย่างง่ายดายในขณะนี้

หมายเหตุ:
- เงาจะไม่ปรากฏใน IB เฉพาะตอนรันไทม์
- อย่างที่ Mazen Kasser พูด

สำหรับผู้ที่ล้มเหลวในการทำให้สิ่งนี้ใช้งานได้ [... ] ตรวจสอบให้แน่ใจว่าไม่ได้clipsToBoundsเปิดใช้งานคลิป Subviews ( )


นี่เป็นคำตอบที่ถูกต้อง - ตั้งค่ารหัสสำหรับอินเทอร์เฟซในอนาคต
Crake

วิธีการแก้ปัญหานี้ทำให้ฉันไปสู่พฤติกรรมที่ไม่ทำงานพร้อมข้อความเตือนต่อไปนี้ (หนึ่งรายการสำหรับแต่ละสถานที่):Failed to set (shadowColor) user defined inspected property on (UICollectionViewCell): [<UICollectionViewCell> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key shadowColor.
Xvolks

1
ฉันต้องนำเข้าUIKitเพื่อให้มันใช้งานได้Foundationการนำเข้าพื้นฐานที่สร้างโดย XCode นั้นไม่เพียงพอ แต่ก็รวบรวมได้ดี ฉันควรจะคัดลอกรหัสต้นฉบับทั้งหมด ขอบคุณ Axel สำหรับวิธีแก้ปัญหาที่ดีนี้
Xvolks

13

ฉันใช้สิ่งนี้เป็นส่วนหนึ่งของสิ่งของ ด้วยสิ่งนี้เราไม่เพียงสามารถตั้งค่าเงา แต่ยังสามารถรับมุมโค้งมนUIViewได้ นอกจากนี้คุณสามารถกำหนดสิ่งที่เงาสีที่คุณต้องการ โดยทั่วไปแล้วชอบสีดำ แต่บางครั้งเมื่อพื้นหลังไม่ใช่สีขาวคุณอาจต้องการอย่างอื่น นี่คือสิ่งที่ฉันใช้ -

in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer 
              radius:(float)r 
              shadow:(BOOL)s
{
    [viewLayer setMasksToBounds:YES];
    [viewLayer setCornerRadius:r];        
    [viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
    [viewLayer setBorderWidth:1.0f];
    if(s)
    {
        [viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
        [viewLayer setShadowOffset:CGSizeMake(0, 0)];
        [viewLayer setShadowOpacity:1];
        [viewLayer setShadowRadius:2.0];
    }
    return;
}

ในการใช้สิ่งนี้เราจำเป็นต้องเรียกสิ่งนี้ - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];


7

สวิฟท์ 3

extension UIView {
    func installShadow() {
        layer.cornerRadius = 2
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 1)
        layer.shadowOpacity = 0.45
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath
        layer.shadowRadius = 1.0
    }
}

ถ้าคุณใช้วิธีนี้ฉันจะพิจารณาเพิ่มพารามิเตอร์เพื่อให้คุณสามารถปรับค่าเพื่อให้เป็นแบบไดนามิก
Josh O'Connor

ฉันไม่ได้ทำอะไรผิดหรือเปล่า?
Nikhil Manapure

@NikhilManapure ไม่มีอะไรเกิดขึ้นอาจเป็นเพราะฟังก์ชั่นinstallShadow()ไม่ได้ถูกเรียกใช้จากviewDidLoad()หรือviewWillAppear()
neoneye

ฉันเรียกมันจากdrawวิธีการรับชมที่มากเกินไป เงากำลังมาอย่างถูกต้อง แต่มุมมนไม่ได้
Nikhil Manapure

@NikhilManapure ไม่มีขอบโค้งมนนี่อาจเป็นเพราะมุมมองเป็นUIStackViewเพียงการจัดวางและไม่มีมุมมอง คุณอาจจะต้องใส่แบบธรรมดาUIViewเป็นภาชนะสำหรับทุกสิ่ง
neoneye

6

หากคุณต้องการใช้กระดานเรื่องราวและไม่ต้องการที่จะพิมพ์ในแอททริบิวต์รันไทม์คุณสามารถสร้างส่วนขยายไปยังมุมมองและทำให้สามารถใช้งานได้ในกระดานเรื่องราว

ขั้นตอนที่ 1 สร้างส่วนขยาย

extension UIView {

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

@IBInspectable var shadowOpacity: Float {
    get {
        return layer.shadowOpacity
    }
    set {
        layer.shadowOpacity = newValue
    }
}

@IBInspectable var shadowOffset: CGSize {
    get {
        return layer.shadowOffset
    }
    set {
        layer.shadowOffset = newValue
    }
}

@IBInspectable var maskToBound: Bool {
    get {
        return layer.masksToBounds
    }
    set {
        layer.masksToBounds = newValue
    }
}
}

ขั้นตอนที่ 2 คุณสามารถใช้คุณสมบัติเหล่านี้ในกระดานเรื่องราวได้แล้วภาพสตอรีบอร์ด


3

สำหรับผู้ที่ล้มเหลวในการทำให้สิ่งนี้ใช้งานได้ (ในฐานะตัวฉันเอง!) หลังจากลองคำตอบทั้งหมดที่นี่เพียงตรวจสอบให้แน่ใจว่าไม่ได้เปิดใช้งานการแสดงตัวอย่างคลิปที่ผู้ตรวจสอบแอตทริบิวต์ ...


1

สวิฟท์ 3

self.paddingView.layer.masksToBounds = false
self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
self.paddingView.layer.shadowRadius = 5
self.paddingView.layer.shadowOpacity = 0.5

1

คุณสามารถใช้ฟังก์ชั่นยูทิลิตี้ของฉันที่สร้างขึ้นสำหรับรัศมีเงาและมุมตามด้านล่าง:

- (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{

    // drop shadow
    [view.layer setShadowRadius:shadowRadius];
    [view.layer setShadowOpacity:shadowOpacity];
    [view.layer setShadowOffset:shadowOffset];
    [view.layer setShadowColor:shadowColor.CGColor];

    // border radius
    [view.layer setCornerRadius:cornerRadius];

    // border
    [view.layer setBorderColor:borderColor.CGColor];
    [view.layer setBorderWidth:borderWidth];
}

หวังว่ามันจะช่วยให้คุณ !!!


1

ตอบทั้งหมดดี แต่ฉันต้องการเพิ่มอีกหนึ่งจุด

หากคุณพบปัญหาเมื่อคุณมีเซลล์ตาราง Deque เซลล์ใหม่มีความไม่ตรงกันในเงาดังนั้นในกรณีนี้คุณต้องวางโค้ดเงาของคุณในวิธี layoutSubviews เพื่อให้มันทำงานได้ดีในทุกสภาวะ

-(void)layoutSubviews{
    [super layoutSubviews];

    [self.contentView setNeedsLayout];
    [self.contentView layoutIfNeeded];
    [VPShadow applyShadowView:self];
}

หรือใน ViewControllers สำหรับรหัสเงาสถานที่เฉพาะภายในวิธีการต่อไปนี้เพื่อให้มันทำงานได้ดี

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

    [self.viewShadow layoutIfNeeded];
    [VPShadow applyShadowView:self.viewShadow];
}

ฉันได้แก้ไขการใช้เงาของฉันสำหรับ devs ใหม่สำหรับแบบฟอร์มทั่วไปเพิ่มเติม:

/*!
 @brief Add shadow to a view.

 @param layer CALayer of the view.

 */
+(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
    UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
    layer.masksToBounds = NO;
    layer.shadowColor = [UIColor blackColor].CGColor;
    layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
    layer.shadowOpacity = alpha;
    layer.shadowRadius = radius;// blur effect
    layer.shadowPath = shadowPath.CGPath;
}

1

สำหรับเพื่อน Xamarians คำตอบรุ่น Xamarin.iOS / C # จะมีลักษณะดังต่อไปนี้:

public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
{
    CGContext currentContext = UIGraphics.GetCurrentContext();
    currentContext.SaveState();
    currentContext.SetShadow(new CGSize(-15, 20), 5);
    base.DrawRect(area, formatter);
    currentContext.RestoreState();                
}

ความแตกต่างหลักคือคุณได้รับอินสแตนซ์CGContextที่คุณเรียกวิธีการที่เหมาะสมโดยตรง


1

คุณสามารถใช้สิ่งนี้Extensionเพื่อเพิ่มเงา

extension UIView {

    func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
    {
        layer.masksToBounds = false
        layer.shadowOffset = offset
        layer.shadowColor = color.cgColor
        layer.shadowRadius = radius
        layer.shadowOpacity = opacity

        let backgroundCGColor = backgroundColor?.cgColor
        backgroundColor = nil
        layer.backgroundColor =  backgroundCGColor
    }
}

คุณสามารถเรียกมันว่าเหมือน

your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)

0

ร่างเงาโดยใช้ IBDesignable และ IBInspectable ใน Swift 4

วิธีใช้งาน

การสาธิต

SKETCH และ XCODE ด้านโดยด้าน

Shadow Exampl

รหัส

@IBDesignable class ShadowView: UIView {

    @IBInspectable var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }

    @IBInspectable var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set {
            layer.shadowOpacity = newValue
        }
    }

    @IBInspectable var shadowOffset: CGPoint {
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }

     }

    @IBInspectable var shadowBlur: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue / 2.0
        }
    }

    @IBInspectable var shadowSpread: CGFloat = 0 {
        didSet {
            if shadowSpread == 0 {
                layer.shadowPath = nil
            } else {
                let dx = -shadowSpread
                let rect = bounds.insetBy(dx: dx, dy: dx)
                layer.shadowPath = UIBezierPath(rect: rect).cgPath
            }
        }
    }
}

เอาท์พุท

DEMO OUTPUT


คุณสามารถปรับปรุงรหัสการลบคำนำหน้าเงาจากคุณลักษณะ เป้าหมายของ ShadowView นั้นชัดเจนเกี่ยวกับการทำงานของเงา อีกสิ่งหนึ่งคือคุณสามารถเพิ่มรัศมีมุมเข้ากับคลาสนี้ได้ (ในวันนี้มุมมองเงาส่วนใหญ่เกี่ยวข้องกับรัศมีมุม)
mathema
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.