Bagaimana cara menggambar bayangan di bawah UIView?

352

Saya mencoba menggambar bayangan di bawah tepi bawah UIViewdi Cocoa Touch. Saya mengerti bahwa saya harus menggunakan CGContextSetShadow()untuk menggambar bayangan, tetapi panduan pemrograman Quartz 2D sedikit kabur:

  1. Simpan status gambar.
  2. Panggil fungsi CGContextSetShadow, melewati nilai yang sesuai.
  3. Lakukan semua gambar yang ingin Anda terapkan bayangan.
  4. Kembalikan keadaan grafis

Saya sudah mencoba yang berikut ini dalam UIViewsubkelas:

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

..tapi ini tidak bekerja untuk saya dan saya agak bingung tentang (a) ke mana harus pergi berikutnya dan (b) jika ada sesuatu yang perlu saya lakukan UIViewuntuk membuat pekerjaan ini?

Fraser Speirs
sumber

Jawaban:

98

Dalam kode Anda saat ini, Anda menyimpan GStatekonteks saat ini, mengkonfigurasinya untuk menggambar bayangan .. dan mengembalikannya seperti semula sebelum Anda mengonfigurasinya untuk menggambar bayangan. Kemudian, akhirnya, Anda meminta implementasi superclass dari drawRect:.

Gambar apa pun yang harus dipengaruhi oleh pengaturan bayangan harus terjadi setelahnya

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

tapi sebelumnya

CGContextRestoreGState(currentContext);

Jadi jika Anda ingin superclass drawRect:'dibungkus' dalam bayangan, lalu bagaimana jika Anda mengatur ulang kode Anda seperti ini?

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

Pendekatan yang jauh lebih mudah adalah dengan menetapkan beberapa atribut layer tampilan pada inisialisasi:

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

Anda perlu mengimpor QuartzCore.

#import <QuartzCore/QuartzCore.h>
ollie
sumber
4
Namun ketahuilah bahwa ini hanya bekerja pada iOS 3.2+ jadi jika aplikasi Anda harus bekerja pada versi lama Anda harus menggunakan solusi Christian atau gambar statis di belakang tampilan jika ini merupakan opsi.
florianbuerger
119
Solusi ini juga membutuhkan penambahan #import <QuartzCore/QuartzCore.h>"ke file .h.
MusiGenesis
26
Pengaturan masksToBoundsuntuk NOakan meniadakan cornerRadius, bukan?
pixelfreak
4
Oke, untuk mengatasinya, backgroundColor perlu diatur pada layer dan tampilan harus transparan.
pixelfreak
@pixelfreak Bagaimana Anda melakukannya? Saya mencoba self.layer.backgroundColor = [[UIColor whiteColor] CGColor];tetapi tidak berhasil. Pandangan mana yang perlu transparan?
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;

Ini akan memperlambat aplikasi. Menambahkan baris berikut dapat meningkatkan kinerja selama tampilan Anda terlihat persegi panjang:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
ZYiOS
sumber
1
Mungkin perlu dicatat bahwa pengoptimalan ini hanya berguna jika tampilan Anda terlihat persegi panjang.
Benjamin Dobell
1
self.layer.shadowPath ... bukannya apa? atau hanya menambahkannya
Chrizzz
2
Cukup tambahkan baris tambahan di samping yang lain.
christophercotton
11
@NathanGaskin - menggambar bayangan adalah operasi yang mahal, jadi misalnya jika aplikasi Anda memungkinkan orientasi antarmuka lain dan Anda mulai memutar perangkat, tanpa menentukan secara eksplisit jalur bayangan itu harus dirender beberapa kali selama animasi yang akan tergantung pada bentuknya, mungkin terlihat memperlambat animasi
Peter Pajchl
9
@BenjaminDobell Baris khusus itu hanya berfungsi untuk persegi panjang tetapi Anda juga bisa membuat jalur non-persegi panjang. Misalnya, jika Anda memiliki persegi panjang bulat, Anda dapat menggunakan bezierPathWithRoundedRect: cornerRadius:
Dan Dyer
160

Solusi yang sama, tetapi hanya untuk mengingatkan Anda: Anda dapat menentukan bayangan langsung di storyboard.

Ex:

masukkan deskripsi gambar di sini

Antzi
sumber
2
Sayangnya, saya pikir CGColor berada di luar jangkauan dari storyboard :(
Antzi
1
cukup tentukan kategori untuk UIViewatau CGLayeryang merupakan UIColorpembungkus untuk CGColorproperti;)
DeFrenZ
1
Tautan ini menjelaskan cara melakukannya: cocoadventures.org/post/104137872754/...
Kamchatka
8
Untuk mempermudah orang menyalin dan menempel: layer.masksToBound, layer.shadowOffset, layer.shadowRadius, layer.shadowOpacity. Kesalahan ketik akan membunuh Anda di sini.
etayluz
3
nilai layer.shadowOpacity harus 0,5 tidak 0,5
Desert Rose
43

Anda dapat mencoba ini .... Anda bisa bermain dengan nilainya. Yang shadowRadiusmenentukan jumlah blur. shadowOffsetmenentukan kemana bayangan pergi.

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

Contoh dengan spread

Contoh dengan spread

Untuk membuat bayangan dasar

    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

Contoh Basic Shadow di Swift 2.0

KELUARAN

O-mkar
sumber
19

Solusi sederhana dan bersih menggunakan Interface Builder

Tambahkan file bernama UIView.swift di proyek Anda (atau cukup tempel ini di file apa pun):

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

Maka ini akan tersedia di Interface Builder untuk setiap tampilan di Utilities Panel> Attributes Inspector:

Panel Utilitas

Anda dapat dengan mudah mengatur bayangan sekarang.

Catatan:
- Bayangan tidak akan muncul di IB, hanya saat runtime.
- Seperti yang dikatakan Mazen Kasser

Bagi mereka yang gagal membuat ini berfungsi [...] pastikan Klip Subviews ( clipsToBounds) tidak diaktifkan

Axel Guilmin
sumber
Ini adalah jawaban yang benar - konfigurasi atas kode untuk antarmuka masa depan
Crake
Solusi ini membawa saya ke perilaku tidak berfungsi dengan pesan peringatan berikut (satu untuk setiap properti):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
Saya harus mengimpor UIKituntuk membuatnya berfungsi, Foundationimpor dasar yang dibuat oleh XCode tidak cukup, tetapi mengkompilasi dengan baik. Saya seharusnya menyalin seluruh kode sumber. Terima kasih Axel untuk solusi yang bagus ini.
Xvolks
13

Saya menggunakan ini sebagai bagian dari utils saya. Dengan ini kita tidak hanya dapat mengatur bayangan tetapi juga bisa mendapatkan sudut bulat untuk apa saja UIView. Anda juga dapat mengatur bayangan warna apa yang Anda inginkan. Biasanya hitam lebih disukai tetapi kadang-kadang, ketika latar belakang non-putih Anda mungkin menginginkan yang lain. Inilah yang saya gunakan -

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

Untuk menggunakan ini kita perlu menyebutnya - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];

Srikar Appalaraju
sumber
7

Cepat 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
    }
}
neoneye
sumber
jika Anda menggunakan metode ini, saya akan mempertimbangkan untuk menambahkan parameter agar Anda dapat menyesuaikan nilai-nilainya begitu dinamis
Josh O'Connor
Itu tidak memberikan sudut bulat kepada saya, apakah saya melakukan sesuatu yang salah?
Nikhil Manapure
@NikhilManapure tidak ada yang terjadi, ini bisa jadi karena fungsinya installShadow()tidak dipanggil dari viewDidLoad()atauviewWillAppear()
neoneye
Saya menyebutnya dari drawmetode tampilan yang terlalu banyak. Bayangan datang dengan benar tetapi sudut bulat tidak.
Nikhil Manapure
@NikhilManapure tidak ada batas bulat, ini bisa jadi karena tampilan adalah UIStackViewtata letak yang hanya melakukan dan tidak memiliki tampilan. Anda mungkin harus memasukkan biasa UIViewsebagai wadah untuk semuanya.
neoneye
6

Jika Anda ingin menggunakan StoryBoard dan tidak ingin terus mengetik atribut runtime, Anda dapat dengan mudah membuat ekstensi untuk dilihat dan membuatnya dapat digunakan di storyboard.

Langkah 1. buat ekstensi

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

langkah 2. Anda sekarang dapat menggunakan atribut ini di storyboardgambar storyboard

king_T
sumber
3

Bagi mereka yang gagal membuat ini berfungsi (Seperti saya!) Setelah mencoba semua jawaban di sini, pastikan Klip Subviews tidak diaktifkan di inspektur Atribut ...

Mazen Kasser
sumber
1

Cepat 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
Josh O'Connor
sumber
1

Anda dapat menggunakan fungsi utilitas saya yang dibuat untuk radius bayangan dan sudut seperti di bawah ini:

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

Semoga ini bisa membantu Anda !!!

Sandip Patel - SM
sumber
1

Semua Jawab dengan baik, tetapi saya ingin menambahkan satu poin lagi

Jika Anda mengalami masalah ketika Anda memiliki sel tabel, Deque sel baru ada ketidakcocokan dalam bayangan sehingga dalam kasus ini, Anda perlu menempatkan kode bayangan Anda dalam metode layoutSubviews sehingga akan berperilaku baik di semua kondisi.

-(void)layoutSubviews{
    [super layoutSubviews];

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

atau di ViewControllers untuk kode tempat bayangan tampilan tertentu di dalam metode berikut ini sehingga berfungsi dengan baik

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

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

Saya telah memodifikasi implementasi bayangan saya untuk pengembang baru untuk formulir yang lebih umum:

/*!
 @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;
}
Wisnu16
sumber
1

Untuk sesama Xamaria, jawaban versi Xamarin.iOS / C # akan terlihat seperti berikut:

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

Perbedaan utama adalah bahwa Anda memperoleh contoh CGContextdi mana Anda secara langsung memanggil metode yang sesuai.

Martin Zikmund
sumber
1

Anda dapat menggunakan ini Extensionuntuk menambahkan bayangan

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

Anda bisa menyebutnya seperti

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

Sketch Shadow Menggunakan IBDesignable dan IBInspectable di Swift 4

BAGAIMANA CARA MENGGUNAKANNYA

DEMO

SISI SKETCH DAN XCODE OLEH SISI

Contoh Shadow

KODE

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

KELUARAN

OUTPUT DEMO

O-mkar
sumber
Anda dapat meningkatkan kode menghapus awalan bayangan dari atribut. Tujuan ShadowView jelas tentang operasi bayangan. Satu hal lagi adalah Anda dapat menambahkan radius sudut ke kelas ini. (Pada hari ini, sebagian besar tampilan bayangan melibatkan radius sudut)
mathema