Làm thế nào để tôi vẽ một cái bóng dưới một UIView?


352

Tôi đang cố vẽ một cái bóng dưới rìa dưới của UIViewCốc Cốc. Tôi hiểu rằng tôi nên sử dụng CGContextSetShadow()để vẽ bóng, nhưng hướng dẫn lập trình Quartz 2D hơi mơ hồ:

  1. Lưu trạng thái đồ họa.
  2. Gọi hàm CGContextSetShadow, truyền các giá trị thích hợp.
  3. Thực hiện tất cả các bản vẽ mà bạn muốn áp dụng bóng.
  4. Khôi phục trạng thái đồ họa

Tôi đã thử những điều sau trong một UIViewlớp con:

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

.. nhưng điều này không hiệu quả với tôi và tôi hơi bế tắc về (a) sẽ đi đâu tiếp theo và (b) nếu có bất cứ điều gì tôi cần phải làm với tôi UIViewđể làm cho công việc này?

Câu trả lời:


98

Trong mã hiện tại của bạn, bạn lưu GStatebối cảnh hiện tại, định cấu hình nó để vẽ bóng .. và khôi phục lại nó trước khi bạn định cấu hình nó để vẽ bóng. Sau đó, cuối cùng, bạn gọi việc thực hiện của siêu lớp drawRect:.

Bất kỳ bản vẽ nào bị ảnh hưởng bởi cài đặt bóng cần phải xảy ra sau

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

nhưng trước đây

CGContextRestoreGState(currentContext);

Vì vậy, nếu bạn muốn các siêu lớp drawRect:được 'bọc' trong một cái bóng, vậy thì nếu bạn sắp xếp lại mã của mình như thế này thì sao?

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

789

Cách tiếp cận dễ dàng hơn nhiều là đặt một số thuộc tính lớp của chế độ xem khi khởi tạo:

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

Bạn cần nhập QuartzCore.

#import <QuartzCore/QuartzCore.h>

4
Nhưng hãy lưu ý rằng điều này chỉ hoạt động trên iOS 3.2+, vì vậy nếu ứng dụng của bạn hoạt động trên phiên bản cũ, bạn phải sử dụng giải pháp của Christian hoặc hình ảnh tĩnh phía sau chế độ xem nếu đây là một tùy chọn.
florianbuerger

119
Giải pháp này cũng yêu cầu thêm #import <QuartzCore/QuartzCore.h>"vào tệp .h.
MusiGenesis

26
Thiết masksToBoundsđể NOsẽ phủ nhận cornerRadius, không có?
pixelfreak

4
Được rồi, để giải quyết điều đó, phông nền cần được đặt trên lớp và khung nhìn cần phải trong suốt.
pixelfreak

@pixelfreak Làm thế nào để bạn làm điều đó? Tôi đã thử self.layer.backgroundColor = [[UIColor whiteColor] CGColor];nhưng không có may mắn. Quan điểm nào cần minh bạch?
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;

Điều này sẽ làm chậm ứng dụng. Thêm dòng sau có thể cải thiện hiệu suất miễn là chế độ xem của bạn rõ ràng là hình chữ nhật:

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

1
Có lẽ đáng chú ý tối ưu hóa này chỉ hữu ích nếu chế độ xem của bạn rõ ràng là hình chữ nhật.
Benjamin Dobell

1
self.layer.shadowPath ... thay vì cái gì? hoặc chỉ cần thêm vào nó
Chrizzz

2
Chỉ cần thêm dòng bổ sung ngoài những người khác.
christopherc Bông

11
@NathanGaskin - vẽ bóng tối là hoạt động đắt tiền, vì vậy, ví dụ nếu ứng dụng của bạn cho phép định hướng giao diện khác và bạn bắt đầu xoay thiết bị, mà không chỉ định rõ ràng đường dẫn của bóng phải được hiển thị nhiều lần trong hoạt hình đó, tùy thuộc vào hình dạng, có thể làm chậm rõ rệt hoạt hình
Peter Pajchl

9
@BenjaminDobell Dòng cụ thể đó chỉ hoạt động cho hình chữ nhật nhưng bạn cũng có thể tạo các đường dẫn không phải hình chữ nhật. Ví dụ: nếu bạn có một hình chữ nhật tròn, bạn có thể sử dụng bezierPathWithRoundedRect: angleRadius:
Dan Dyer

160

Cùng một giải pháp, nhưng chỉ để nhắc nhở bạn: Bạn có thể xác định bóng trực tiếp trong bảng phân cảnh.

Ví dụ:

nhập mô tả hình ảnh ở đây


2
Đáng buồn thay, tôi nghĩ CGColor nằm ngoài tầm với của bảng phân cảnh :(
Antzi

1
chỉ cần xác định một danh mục cho UIViewhoặc CGLayerđó là một UIColortrình bao bọc cho CGColortài sản;)
DeFrenZ

1
Liên kết này mô tả cách thực hiện: cocoadventures.org/post/104137872754/ triệt
Kamchatka

8
Để giúp mọi người dễ dàng sao chép và dán: layer.masksToBound, layer.shadow Offerset, layer.shadowRadius, layer.shadowOpacity. Typose sẽ giết bạn ở đây.
etayluz

3
giá trị layer.shadowOpacity phải là 0,5 chứ không phải 0,5
Hoa hồng sa mạc

43

Bạn có thể thử điều này .... bạn có thể chơi với các giá trị. Các shadowRadiusmệnh lệnh của số lượng mờ. shadowOffsetra lệnh nơi bóng tối đi

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

Ví dụ với lây lan

Ví dụ với lây lan

Để tạo bóng cơ bản

    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

Ví dụ về bóng cơ bản trong Swift 2.0

ĐẦU RA


19

Giải pháp đơn giản và sạch sẽ bằng Interface Builder

Thêm một tệp có tên UIView.swift trong dự án của bạn (hoặc chỉ dán tệp này vào bất kỳ tệp nào):

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

Sau đó, tính năng này sẽ có sẵn trong Trình tạo giao diện cho mọi chế độ xem trong Bảng tiện ích> Trình theo dõi thuộc tính:

Bảng tiện ích

Bạn có thể dễ dàng thiết lập bóng ngay bây giờ.

Lưu ý:
- Bóng sẽ không xuất hiện trong IB, chỉ trong thời gian chạy.
- Như Mazen Kasser đã nói

Đối với những người thất bại trong việc làm điều này hoạt động [...], hãy đảm bảo Clip Subview ( clipsToBounds) không được bật


Đây là câu trả lời đúng - cấu hình qua mã cho các giao diện trong tương lai
Crake 17/03/2016

Giải pháp này đưa tôi đến một hành vi không hoạt động với thông điệp cảnh báo sau (một cho mỗi thuộc tính):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
Tôi đã phải nhập UIKitđể làm cho nó hoạt động, Foundationnhập cơ bản được tạo bởi XCode là không đủ, nhưng biên dịch tốt. Tôi nên đã sao chép toàn bộ mã nguồn. Cảm ơn bạn Axel cho giải pháp tốt đẹp này.
Xvolks

13

Tôi sử dụng điều này như một phần của đồ dùng của tôi. Với điều này, chúng tôi không chỉ có thể đặt bóng mà còn có thể có được một góc tròn cho bất kỳ UIView. Ngoài ra, bạn có thể thiết lập màu bóng bạn thích. Thông thường màu đen được ưa thích nhưng đôi khi, khi nền không phải màu trắng, bạn có thể muốn một cái gì đó khác. Đây là những gì tôi sử dụng -

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

Để sử dụng chúng ta cần gọi nó - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];


7

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

nếu bạn đang sử dụng phương pháp này, tôi sẽ xem xét thêm các tham số để bạn có thể điều chỉnh các giá trị sao cho linh hoạt
Josh O'Connor

Nó không tạo ra góc tròn cho tôi, tôi có làm gì sai không?
Nikhil Manapure

@NikhilQuản lý không có gì xảy ra, điều này có thể là do chức năng installShadow()không được gọi từ viewDidLoad()hoặcviewWillAppear()
neoneye

Tôi đã gọi nó từ drawphương pháp xem quá tải . Shadow đang đến một cách chính xác nhưng góc tròn thì không.
Nikhil Manapure

@NikhilManapure không có viền tròn, điều này có thể là do chế độ xem UIStackViewchỉ là bố cục và không có chế độ xem. Bạn có thể phải chèn thường xuyên UIViewnhư một container cho tất cả mọi thứ.
neoneye

6

Nếu bạn muốn sử dụng StoryBoard và không muốn tiếp tục nhập các thuộc tính thời gian chạy, bạn có thể dễ dàng tạo tiện ích mở rộng cho chế độ xem và làm cho chúng có thể sử dụng được trong bảng phân cảnh.

Bước 1. tạo phần mở rộng

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

Bước 2. bây giờ bạn có thể sử dụng các thuộc tính này trong bảng phân cảnhhình ảnh cốt truyện


3

Đối với những người thất bại trong việc làm điều này hoạt động (Như bản thân tôi!) Sau khi thử tất cả các câu trả lời ở đây, chỉ cần đảm bảo Clip Subview không được bật tại trình kiểm tra thuộc tính ...


1

Swift 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

Bạn có thể sử dụng chức năng tiện ích của tôi được tạo cho bán kính bóng và góc như bên dưới:

- (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];
}

Hy vọng nó sẽ giúp bạn !!!


1

Tất cả đều trả lời tốt nhưng tôi muốn thêm một điểm nữa

Nếu bạn gặp sự cố khi bạn có các ô trong bảng, Deque một ô mới có bóng không khớp, vì vậy trong trường hợp này, bạn cần đặt mã bóng của mình trong phương thức layoutSubview để nó hoạt động tốt trong mọi điều kiện.

-(void)layoutSubviews{
    [super layoutSubviews];

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

hoặc trong ViewControllers để biết mã bóng của vị trí xem cụ thể bên trong phương thức sau để nó hoạt động tốt

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

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

Tôi đã sửa đổi triển khai bóng của mình cho các nhà phát triển mới cho dạng tổng quát hơn:

/*!
 @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

Đối với đồng nghiệp Xamarians, phiên bản Xamarin.iOS / C # của câu trả lời sẽ giống như sau:

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

Sự khác biệt chính là bạn có được một thể CGContexthiện mà bạn gọi trực tiếp các phương thức thích hợp.


1

Bạn có thể sử dụng điều này Extensionđể thêm bóng

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

bạn có thể gọi nó như thế

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

0

Phác thảo bóng bằng cách sử dụng IBDesignable và IBInspectable trong Swift 4

LÀM THẾ NÀO ĐỂ SỬ DỤNG NÓ

BẢN GIỚI THIỆU

SKETCH VÀ XCODE BÊN BÊN

Kỳ thi bóng tối

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

ĐẦU RA

ĐẦU RA DEMO


Bạn có thể cải thiện mã loại bỏ các tiền tố bóng từ các thuộc tính. Mục đích của ShadowView là rõ ràng về hoạt động của bóng tối. Một điều nữa là bạn có thể thêm bán kính góc cho lớp này. (Trong ngày hôm nay, hầu hết các chế độ xem bóng liên quan đến bán kính góc)
mathema
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.