Efek bayangan batin pada lapisan UIView?


92

Saya memiliki CALayer berikut:

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(8, 57, 296, 30);
gradient.cornerRadius = 3.0f;
gradient.colors = [NSArray arrayWithObjects:(id)[RGB(130, 0, 140) CGColor], (id)[RGB(108, 0, 120) CGColor], nil];
[self.layer insertSublayer:gradient atIndex:0];

Saya ingin menambahkan efek bayangan batin padanya, tapi saya tidak begitu yakin bagaimana melakukan ini. Saya kira saya akan diminta untuk menggambar drawRect, namun ini akan menambahkan lapisan di atas objek UIView lainnya, karena itu seharusnya menjadi bilah di belakang beberapa tombol, jadi saya bingung apa yang harus dilakukan?

Saya bisa menambahkan lapisan lain, tetapi sekali lagi, tidak yakin bagaimana cara mencapai efek bayangan dalam (seperti ini:

masukkan deskripsi gambar di sini

Bantuan dihargai ...

Jawaban:


108

Bagi orang lain yang bertanya-tanya bagaimana cara menggambar bayangan dalam menggunakan Core Graphics sesuai saran Costique, maka begini caranya: (di iOS sesuaikan sesuai kebutuhan)

Dalam metode drawRect: ...

CGRect bounds = [self bounds];
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat radius = 0.5f * CGRectGetHeight(bounds);


// Create the "visible" path, which will be the shape that gets the inner shadow
// In this case it's just a rounded rect, but could be as complex as your want
CGMutablePathRef visiblePath = CGPathCreateMutable();
CGRect innerRect = CGRectInset(bounds, radius, radius);
CGPathMoveToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x + innerRect.size.width, bounds.origin.y);
CGPathAddArcToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, bounds.origin.y, bounds.origin.x + bounds.size.width, innerRect.origin.y, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x + bounds.size.width, innerRect.origin.y + innerRect.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x + bounds.size.width, bounds.origin.y + bounds.size.height, innerRect.origin.x + innerRect.size.width, bounds.origin.y + bounds.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.origin.x, bounds.origin.y + bounds.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y + bounds.size.height, bounds.origin.x, innerRect.origin.y + innerRect.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.origin.x, innerRect.origin.y);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.origin.x, bounds.origin.y, innerRect.origin.x, bounds.origin.y, radius);
CGPathCloseSubpath(visiblePath);

// Fill this path
UIColor *aColor = [UIColor redColor];
[aColor setFill];
CGContextAddPath(context, visiblePath);
CGContextFillPath(context);


// Now create a larger rectangle, which we're going to subtract the visible path from
// and apply a shadow
CGMutablePathRef path = CGPathCreateMutable();
//(when drawing the shadow for a path whichs bounding box is not known pass "CGPathGetPathBoundingBox(visiblePath)" instead of "bounds" in the following line:)
//-42 cuould just be any offset > 0
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the visible path (so that it gets subtracted for the shadow)
CGPathAddPath(path, NULL, visiblePath);
CGPathCloseSubpath(path);

// Add the visible paths as the clipping path to the context
CGContextAddPath(context, visiblePath); 
CGContextClip(context);         


// Now setup the shadow properties on the context
aColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 3.0f, [aColor CGColor]);   

// Now fill the rectangle, so the shadow gets drawn
[aColor setFill];   
CGContextSaveGState(context);   
CGContextAddPath(context, path);
CGContextEOFillPath(context);

// Release the paths
CGPathRelease(path);    
CGPathRelease(visiblePath);

Jadi, intinya ada langkah-langkah berikut:

  1. Ciptakan jalan Anda
  2. Atur warna isian yang Anda inginkan, tambahkan jalur ini ke konteks, dan isi konteksnya
  3. Sekarang buat persegi panjang yang lebih besar yang dapat mengikat jalur yang terlihat. Sebelum menutup jalur ini, tambahkan jalur yang terlihat. Kemudian tutup jalurnya, sehingga Anda membuat bentuk dengan jalur yang terlihat dikurangi darinya. Anda mungkin ingin menyelidiki metode pengisian (belitan genap / ganjil bukan nol) bergantung pada cara Anda membuat jalur ini. Intinya, untuk mendapatkan subpath untuk "mengurangi" saat Anda menjumlahkannya, Anda perlu menggambarnya (atau lebih tepatnya membangunnya) ke arah yang berlawanan, satu searah jarum jam dan yang lainnya berlawanan arah jarum jam.
  4. Kemudian Anda perlu mengatur jalur yang terlihat sebagai jalur kliping pada konteks, sehingga Anda tidak menggambar apa pun di luarnya ke layar.
  5. Kemudian atur bayangan pada konteksnya, yang mencakup offset, blur, dan warna.
  6. Kemudian isi bentuk besar dengan lubang di dalamnya. Warnanya tidak masalah, karena jika Anda melakukan semuanya dengan benar, Anda tidak akan melihat warna ini, hanya bayangannya.

Terima kasih, tetapi apakah mungkin untuk menyesuaikan radius? Saat ini didasarkan pada batas, tetapi saya ingin berdasarkan pada radius yang ditetapkan sebagai gantinya (seperti 5.0f). Dengan kode di atas, itu terlalu membulat.
runmad

2
@runmad Anda dapat membuat CGPath yang terlihat apa pun yang Anda inginkan, contoh yang digunakan di sini hanyalah, contoh, dipilih agar singkat. Jika Anda ingin membuat persegi dengan sudut bulat, Anda dapat melakukan sesuatu seperti: CGPath visiblePath = [UIBezierPath bezierPathWithRoundedRect: rect cornerRadius: radius] .CGPath Harapan yang membantu.
Daniel Thorpe

4
@DanielThorpe: +1 untuk jawaban yang bagus. Saya memperbaiki kode jalur persegi bulat (milik Anda rusak saat mengubah radius) dan menyederhanakan kode jalur persegi luar. Harap Anda tidak keberatan.
Regexident

Bagaimana saya bisa mengatur bayangan dalam dengan benar dari 4 arah, bukan hanya 2?
Protocole

@Protocole Anda dapat menyetel offset ke {0,0}, tetapi gunakan radius bayangan katakanlah, 4.f.
Daniel Thorpe

47

Saya tahu saya terlambat ke pesta ini, tetapi ini akan membantu saya menemukan lebih awal dalam perjalanan saya ...

Untuk memberikan kredit di mana kredit jatuh tempo, ini pada dasarnya adalah modifikasi dari elaborasi Daniel Thorpe tentang solusi Costique untuk mengurangi wilayah yang lebih kecil dari wilayah yang lebih besar. Versi ini untuk mereka yang menggunakan komposisi lapisan, bukan menimpa-drawRect:

The CAShapeLayerkelas dapat digunakan untuk mencapai efek yang sama:

CAShapeLayer* shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:[self bounds]];

// Standard shadow stuff
[shadowLayer setShadowColor:[[UIColor colorWithWhite:0 alpha:1] CGColor]];
[shadowLayer setShadowOffset:CGSizeMake(0.0f, 0.0f)];
[shadowLayer setShadowOpacity:1.0f];
[shadowLayer setShadowRadius:5];

// Causes the inner region in this example to NOT be filled.
[shadowLayer setFillRule:kCAFillRuleEvenOdd];

// Create the larger rectangle path.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the inner path so it's subtracted from the outer path.
// someInnerPath could be a simple bounds rect, or maybe
// a rounded one for some extra fanciness.
CGPathAddPath(path, NULL, someInnerPath);
CGPathCloseSubpath(path);

[shadowLayer setPath:path];
CGPathRelease(path);

[[self layer] addSublayer:shadowLayer];

Pada titik ini, jika lapisan induk Anda tidak menutupi batasnya, Anda akan melihat area ekstra dari lapisan topeng di sekitar tepi lapisan. Ini akan menjadi 42 piksel hitam jika Anda baru saja menyalin contoh secara langsung. Untuk menghilangkannya, Anda cukup menggunakan yang lain CAShapeLayerdengan jalur yang sama dan mengaturnya sebagai topeng lapisan bayangan:

CAShapeLayer* maskLayer = [CAShapeLayer layer];
[maskLayer setPath:someInnerPath];
[shadowLayer setMask:maskLayer];

Saya belum membandingkannya sendiri, tetapi saya menduga bahwa menggunakan pendekatan ini dalam hubungannya dengan rasterisasi lebih berkinerja daripada menimpa -drawRect:.


3
someInnerPath? Bisakah Anda menjelaskannya lebih lanjut.
Moe

4
@Moe Ini bisa menjadi CGPath sewenang-wenang yang Anda inginkan. [[UIBezierPath pathWithRect:[shadowLayer bounds]] CGPath]menjadi pilihan paling sederhana.
Matt Wilding

Bersulang untuk itu Matt :-)
Moe

Saya mendapatkan persegi panjang hitam (luar) untuk shadowLayer.path yang menggambar bayangan bagian dalam dengan benar. Bagaimana cara menghilangkannya (persegi panjang luar hitam)? Sepertinya Anda hanya dapat mengatur fillColor di dalam konteks & Anda tidak menggunakannya.
Olivier

11
Ini bekerja dengan sangat baik! Saya mengunggah ke github dengan beberapa tambahan. Selamat mencoba :) github.com/inamiy/YIInnerShadowView
inamiy

35

Anda dapat menggambar bayangan bagian dalam dengan Grafik Inti dengan membuat jalur persegi panjang besar di luar batas, mengurangi jalur persegi panjang seukuran batas dan mengisi jalur yang dihasilkan dengan bayangan "normal" aktif.

Namun, karena Anda perlu menggabungkannya dengan lapisan gradien, saya pikir solusi yang lebih mudah adalah membuat gambar PNG transparan 9 bagian dari bayangan bagian dalam dan merentangkannya ke ukuran yang tepat. Gambar bayangan 9 bagian akan terlihat seperti ini (ukurannya 21x21 piksel):

teks alt

CALayer *innerShadowLayer = [CALayer layer];
innerShadowLayer.contents = (id)[UIImage imageNamed: @"innershadow.png"].CGImage;
innerShadowLayer.contentsCenter = CGRectMake(10.0f/21.0f, 10.0f/21.0f, 1.0f/21.0f, 1.0f/21.0f);

Kemudian setel bingkai innerShadowLayer dan itu harus meregangkan bayangan dengan benar.


Ya, saya kira Anda benar. Hanya ingin lapisannya serata mungkin. Saya dapat membuat gambar di Photoshop dengan tampilan bayangan dalam dan gradien, saya hanya memiliki masalah dengan warna yang cocok hingga 100% pada perangkat saat menggunakan gambar.
runmad

Ya, itu masalah dengan semua gradien dan bayangan, saya tidak bisa mereproduksi efek Photoshop 1: 1 ini di iOS, sekeras yang saya coba.
Costique

30

Versi yang disederhanakan hanya menggunakan CALayer, di Swift:

import UIKit

final class FrameView : UIView {
    init() {
        super.init(frame: CGRect.zero)
        backgroundColor = UIColor.white
    }

    @available(*, unavailable)
    required init?(coder decoder: NSCoder) { fatalError("unavailable") }

    override func layoutSubviews() {
        super.layoutSubviews()
        addInnerShadow()
    }

    private func addInnerShadow() {
        let innerShadow = CALayer()
        innerShadow.frame = bounds
        // Shadow path (1pt ring around bounds)
        let path = UIBezierPath(rect: innerShadow.bounds.insetBy(dx: -1, dy: -1))
        let cutout = UIBezierPath(rect: innerShadow.bounds).reversing()
        path.append(cutout)
        innerShadow.shadowPath = path.cgPath
        innerShadow.masksToBounds = true
        // Shadow properties
        innerShadow.shadowColor = UIColor(white: 0, alpha: 1).cgColor // UIColor(red: 0.71, green: 0.77, blue: 0.81, alpha: 1.0).cgColor
        innerShadow.shadowOffset = CGSize.zero
        innerShadow.shadowOpacity = 1
        innerShadow.shadowRadius = 3
        // Add
        layer.addSublayer(innerShadow)
    }
}

Perhatikan bahwa lapisan innerShadow tidak boleh memiliki warna latar belakang buram karena ini akan ditampilkan di depan bayangan.


Baris terakhir berisi 'lapisan'. Dari mana asalnya
Charlie Seligman

@CharlieSeligman Ini adalah lapisan induk, yang bisa berupa lapisan apa saja. Anda dapat menggunakan lapisan khusus atau lapisan tampilan (UIView memiliki properti lapisan).
Patrick Pijnappel

seharusnya let innerShadow = CALayer(); innerShadow.frame = bounds. Tanpa batas yang tepat, itu tidak akan menarik bayangan yang tepat. Terima kasih
haik.ampardjian

@noir_eagle Benar, meskipun Anda mungkin ingin mengaturnya agar layoutSubviews()tetap sinkron
Patrick Pijnappel

Baik! Baik di layoutSubviews()atau didraw(_ rect)
haik.ampardjian

24

Agak berputar-putar, tetapi menghindari keharusan menggunakan gambar (baca: mudah mengubah warna, radius bayangan, dll.) Dan itu hanya beberapa baris kode.

  1. Tambahkan UIImageView sebagai subview pertama dari UIView tempat Anda ingin dropshadow. Saya menggunakan IB, tetapi Anda dapat melakukan hal yang sama secara terprogram.

  2. Mengasumsikan referensi ke UIImageView adalah 'innerShadow'

`

[[innerShadow layer] setMasksToBounds:YES];
[[innerShadow layer] setCornerRadius:12.0f];        
[[innerShadow layer] setBorderColor:[UIColorFromRGB(180, 180, 180) CGColor]];
[[innerShadow layer] setBorderWidth:1.0f];
[[innerShadow layer] setShadowColor:[UIColorFromRGB(0, 0, 0) CGColor]];
[[innerShadow layer] setShadowOffset:CGSizeMake(0, 0)];
[[innerShadow layer] setShadowOpacity:1];
[[innerShadow layer] setShadowRadius:2.0];

Peringatan: Anda harus memiliki perbatasan, atau bayangan tidak muncul. [UIColor clearColor] tidak berfungsi. Dalam contoh, saya menggunakan warna yang berbeda, tetapi Anda dapat mengacaukannya untuk membuatnya memiliki warna yang sama seperti awal bayangan. :)

Lihat komentar bbrame di bawah tentang UIColorFromRGBmakro.


Saya meninggalkannya tetapi menganggap Anda akan melakukan ini sebagai bagian dari menambahkan imageview - pastikan untuk mengatur frame ke persegi yang sama dengan induk UIView. Jika Anda menggunakan IB, atur struts dan pegas ke kanan untuk memiliki ukuran bayangan dengan tampilan jika Anda akan mengubah bingkai tampilan induk. Dalam kode harus ada topeng pengubah ukuran Anda dapat ATAU melakukan hal yang sama, AFAIK.
jinglesthula

Ini adalah cara termudah sekarang, tetapi perlu diketahui bahwa metode bayangan CALayer hanya tersedia di iOS 3.2 dan yang lebih baru. Saya mendukung 3.1, jadi saya menyertakan setelan atribut ini dalam if ([layer respondsToSelector: @selector (setShadowColor :)]) {
DougW

Ini sepertinya tidak berhasil untuk saya. Setidaknya di xcode 4.2 dan ios simulator 4.3. Untuk membuat bayangan muncul saya harus menambahkan warna latar belakang ... di mana titik dropshadow hanya muncul di luar.
Andrea

@Andrea - ingatlah peringatan yang saya sebutkan di atas. Saya pikir warna latar belakang atau batas mungkin memiliki efek yang sama dengan 'memberikan sesuatu untuk menambahkan bayangan'. Adapun itu muncul di luar, jika UIImageView bukan subview dari yang Anda inginkan bayangan batinnya, mungkin itu - saya harus melihat kode Anda untuk melihatnya.
jinglesthula

Hanya untuk memperbaiki pernyataan saya sebelumnya ... kodenya benar-benar berfungsi ... Saya melewatkan sesuatu tetapi sayangnya saya tidak dapat mengingatnya sekarang. :) Jadi ... terima kasih telah membagikan potongan kode ini.
Andrea

17

Lebih baik terlambat daripada tidak sama sekali...

Berikut pendekatan lain, mungkin tidak lebih baik dari yang sudah diposting, tapi bagus & sederhana -

-(void)drawInnerShadowOnView:(UIView *)view
{
    UIImageView *innerShadowView = [[UIImageView alloc] initWithFrame:view.bounds];

    innerShadowView.contentMode = UIViewContentModeScaleToFill;
    innerShadowView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [view addSubview:innerShadowView];

    [innerShadowView.layer setMasksToBounds:YES];

    [innerShadowView.layer setBorderColor:[UIColor lightGrayColor].CGColor];
    [innerShadowView.layer setShadowColor:[UIColor blackColor].CGColor];
    [innerShadowView.layer setBorderWidth:1.0f];

    [innerShadowView.layer setShadowOffset:CGSizeMake(0, 0)];
    [innerShadowView.layer setShadowOpacity:1.0];

    // this is the inner shadow thickness
    [innerShadowView.layer setShadowRadius:1.5];
}

@SomaMan apakah mungkin untuk mengatur bayangan dengan sisi tertentu saja? Seperti hanya di atas atau atas / bawah atau atas / kanan dll ..
Mitesh Dobareeya

8

Alih-alih menggambar bayangan dalam dengan drawRect atau menambahkan UIView ke View. Anda dapat langsung menambahkan CALayer ke perbatasan, misalnya: jika saya ingin efek bayangan di bagian bawah UIView V.

innerShadowOwnerLayer = [[CALayer alloc]init];
innerShadowOwnerLayer.frame = CGRectMake(0, V.frame.size.height+2, V.frame.size.width, 2);
innerShadowOwnerLayer.backgroundColor = [UIColor whiteColor].CGColor;

innerShadowOwnerLayer.shadowColor = [UIColor blackColor].CGColor;
innerShadowOwnerLayer.shadowOffset = CGSizeMake(0, 0);
innerShadowOwnerLayer.shadowRadius = 10.0;
innerShadowOwnerLayer.shadowOpacity = 0.7;

[V.layer addSubLayer:innerShadowOwnerLayer];

Ini menambahkan bayangan bagian dalam bawah untuk target UIView


6

Berikut ini adalah versi swift, change startPointand endPointmake it on each side.

        let layer = CAGradientLayer()
        layer.startPoint    = CGPointMake(0.5, 0.0);
        layer.endPoint      = CGPointMake(0.5, 1.0);
        layer.colors        = [UIColor(white: 0.1, alpha: 1.0).CGColor, UIColor(white: 0.1, alpha: 0.5).CGColor, UIColor.clearColor().CGColor]
        layer.locations     = [0.05, 0.2, 1.0 ]
        layer.frame         = CGRectMake(0, 0, self.view.frame.width, 60)
        self.view.layer.insertSublayer(layer, atIndex: 0)

Bekerja untuk saya !! Terima kasih.
iUser

5

Ini adalah solusi Anda, yang telah saya ekspor dari PaintCode :

-(void) drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    //// Shadow Declarations
    UIColor* shadow = UIColor.whiteColor;
    CGSize shadowOffset = CGSizeMake(0, 0);
    CGFloat shadowBlurRadius = 10;

    //// Rectangle Drawing
    UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: self.bounds];
    [[UIColor blackColor] setFill];
    [rectanglePath fill];

    ////// Rectangle Inner Shadow
    CGContextSaveGState(context);
    UIRectClip(rectanglePath.bounds);
    CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);

    CGContextSetAlpha(context, CGColorGetAlpha([shadow CGColor]));
    CGContextBeginTransparencyLayer(context, NULL);
    {
        UIColor* opaqueShadow = [shadow colorWithAlphaComponent: 1];
        CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, [opaqueShadow CGColor]);
        CGContextSetBlendMode(context, kCGBlendModeSourceOut);
        CGContextBeginTransparencyLayer(context, NULL);

        [opaqueShadow setFill];
        [rectanglePath fill];

        CGContextEndTransparencyLayer(context);
    }
    CGContextEndTransparencyLayer(context);
    CGContextRestoreGState(context);
}

3

Saya sangat terlambat ke pesta tetapi saya ingin memberikan kembali kepada komunitas .. Ini adalah metode yang saya tulis untuk menghapus Gambar latar belakang UITextField karena saya menyediakan Perpustakaan Statis dan TANPA Sumber Daya ... Saya menggunakan ini untuk layar Entri PIN dari empat instance UITextField yang dapat menampilkan Satu karakter mentah atau (BOOL) [self isUsingBullets] atau (BOOL) [self usingAsterisks] di ViewController. Aplikasi untuk iPhone / iPhone retina / iPad / iPad Retina jadi saya tidak perlu menyediakan empat gambar ...

#import <QuartzCore/QuartzCore.h>

- (void)setTextFieldInnerGradient:(UITextField *)textField
{

    [textField setSecureTextEntry:self.isUsingBullets];
    [textField setBackgroundColor:[UIColor blackColor]];
    [textField setTextColor:[UIColor blackColor]];
    [textField setBorderStyle:UITextBorderStyleNone];
    [textField setClipsToBounds:YES];

    [textField.layer setBorderColor:[[UIColor blackColor] CGColor]];
    [textField.layer setBorderWidth:1.0f];

    // make a gradient off-white background
    CAGradientLayer *gradient = [CAGradientLayer layer];
    CGRect gradRect = CGRectInset([textField bounds], 3, 3);    // Reduce Width and Height and center layer
    gradRect.size.height += 2;  // minimise Bottom shadow, rely on clipping to remove these 2 pts.

    gradient.frame = gradRect;
    struct CGColor *topColor = [UIColor colorWithWhite:0.6f alpha:1.0f].CGColor;
    struct CGColor *bottomColor = [UIColor colorWithWhite:0.9f alpha:1.0f].CGColor;
    // We need to use this fancy __bridge object in order to get the array we want.
    gradient.colors = [NSArray arrayWithObjects:(__bridge id)topColor, (__bridge id)bottomColor, nil];
    [gradient setCornerRadius:4.0f];
    [gradient setShadowOffset:CGSizeMake(0, 0)];
    [gradient setShadowColor:[[UIColor whiteColor] CGColor]];
    [gradient setShadowOpacity:1.0f];
    [gradient setShadowRadius:3.0f];

    // Now we need to Blur the edges of this layer "so it blends"
    // This rasterizes the view down to 4x4 pixel chunks then scales it back up using bilinear filtering...
    // it's EXTREMELY fast and looks ok if you are just wanting to blur a background view under a modal view.
    // To undo it, just set the rasterization scale back to 1.0 or turn off rasterization.
    [gradient setRasterizationScale:0.25];
    [gradient setShouldRasterize:YES];

    [textField.layer insertSublayer:gradient atIndex:0];

    if (self.usingAsterisks) {
        [textField setFont:[UIFont systemFontOfSize:80.0]];
    } else {
        [textField setFont:[UIFont systemFontOfSize:40.0]];
    }
    [textField setTextAlignment:UITextAlignmentCenter];
    [textField setEnabled:NO];
}

Saya harap ini membantu seseorang karena forum ini telah membantu saya.


3

Periksa artikel hebat Inner Shadows in Quartz oleh Chris Emery yang menjelaskan bagaimana bayangan bagian dalam digambar oleh PaintCode dan memberikan cuplikan kode yang bersih dan rapi:

- (void)drawInnerShadowInContext:(CGContextRef)context
                        withPath:(CGPathRef)path
                     shadowColor:(CGColorRef)shadowColor
                          offset:(CGSize)offset
                      blurRadius:(CGFloat)blurRadius 
{
    CGContextSaveGState(context);

    CGContextAddPath(context, path);
    CGContextClip(context);

    CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);

    CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
    CGContextBeginTransparencyLayer(context, NULL);
        CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
        CGContextSetBlendMode(context, kCGBlendModeSourceOut);
        CGContextSetFillColorWithColor(context, opaqueShadowColor);
        CGContextAddPath(context, path);
        CGContextFillPath(context);
    CGContextEndTransparencyLayer(context);

    CGContextRestoreGState(context);

    CGColorRelease(opaqueShadowColor);
}

3

Inilah solusi saya di Swift 4.2. Mau coba?

final class ACInnerShadowLayer : CAShapeLayer {

  var innerShadowColor: CGColor? = UIColor.black.cgColor {
    didSet { setNeedsDisplay() }
  }

  var innerShadowOffset: CGSize = .zero {
    didSet { setNeedsDisplay() }
  }

  var innerShadowRadius: CGFloat = 8 {
    didSet { setNeedsDisplay() }
  }

  var innerShadowOpacity: Float = 1 {
    didSet { setNeedsDisplay() }
  }

  override init() {
    super.init()

    masksToBounds = true
    contentsScale = UIScreen.main.scale

    setNeedsDisplay()
  }

  override init(layer: Any) {
      if let layer = layer as? InnerShadowLayer {
          innerShadowColor = layer.innerShadowColor
          innerShadowOffset = layer.innerShadowOffset
          innerShadowRadius = layer.innerShadowRadius
          innerShadowOpacity = layer.innerShadowOpacity
      }
      super.init(layer: layer)
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func draw(in ctx: CGContext) {
    ctx.setAllowsAntialiasing(true)
    ctx.setShouldAntialias(true)
    ctx.interpolationQuality = .high

    let colorspace = CGColorSpaceCreateDeviceRGB()

    var rect = bounds
    var radius = cornerRadius

    if borderWidth != 0 {
      rect = rect.insetBy(dx: borderWidth, dy: borderWidth)
      radius -= borderWidth
      radius = max(radius, 0)
    }

    let innerShadowPath = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
    ctx.addPath(innerShadowPath)
    ctx.clip()

    let shadowPath = CGMutablePath()
    let shadowRect = rect.insetBy(dx: -rect.size.width, dy: -rect.size.width)
    shadowPath.addRect(shadowRect)
    shadowPath.addPath(innerShadowPath)
    shadowPath.closeSubpath()

    if let innerShadowColor = innerShadowColor, let oldComponents = innerShadowColor.components {
      var newComponets = Array<CGFloat>(repeating: 0, count: 4) // [0, 0, 0, 0] as [CGFloat]
      let numberOfComponents = innerShadowColor.numberOfComponents

      switch numberOfComponents {
      case 2:
        newComponets[0] = oldComponents[0]
        newComponets[1] = oldComponents[0]
        newComponets[2] = oldComponents[0]
        newComponets[3] = oldComponents[1] * CGFloat(innerShadowOpacity)
      case 4:
        newComponets[0] = oldComponents[0]
        newComponets[1] = oldComponents[1]
        newComponets[2] = oldComponents[2]
        newComponets[3] = oldComponents[3] * CGFloat(innerShadowOpacity)
      default:
        break
      }

      if let innerShadowColorWithMultipliedAlpha = CGColor(colorSpace: colorspace, components: newComponets) {
        ctx.setFillColor(innerShadowColorWithMultipliedAlpha)
        ctx.setShadow(offset: innerShadowOffset, blur: innerShadowRadius, color: innerShadowColorWithMultipliedAlpha)
        ctx.addPath(shadowPath)
        ctx.fillPath(using: .evenOdd)
      }
    } 
  }
}

Bagaimana jika saya tidak menggunakannya sebagai kelas terpisah, tetapi seperti menggunakan dalam kode saya, konteks (ctx) adalah nihil ketika saya mendapatkan ini:let ctx = UIGraphicsGetCurrentContext
Mohsin Khubaib Ahmed

@MohsinKhubaibAhmed Anda bisa mendapatkan konteks saat ini dengan metode UIGraphicsGetCurrentContext untuk mengambil ketika beberapa tampilan mendorong konteks mereka ke tumpukan.
Arco

@Arco Saya mengalami masalah saat memutar perangkat. Saya menambahkan 'override convenience init (layer: Any) {self.init ()}'. Sekarang tidak ada kesalahan yang ditampilkan!
Yuma Technical Inc.

Menambahkan init (layer: Any) untuk memperbaiki kerusakan.
Nik Kov

2

Solusi yang dapat diskalakan menggunakan CALayer di Swift

Dengan yang dijelaskan InnerShadowLayer Anda juga dapat mengaktifkan bayangan dalam untuk tepi tertentu saja, tidak termasuk yang lain. (misalnya, Anda dapat mengaktifkan bayangan dalam hanya di tepi kiri dan atas tampilan Anda)

Anda kemudian dapat menambahkan a InnerShadowLayerke tampilan Anda menggunakan:

init(...) {

    // ... your initialization code ...

    super.init(frame: .zero)
    layer.addSublayer(shadowLayer)
}

public override func layoutSubviews() {
    super.layoutSubviews()
    shadowLayer.frame = bounds
}

InnerShadowLayer penerapan

/// Shadow is a struct defining the different kinds of shadows
public struct Shadow {
    let x: CGFloat
    let y: CGFloat
    let blur: CGFloat
    let opacity: CGFloat
    let color: UIColor
}

/// A layer that applies an inner shadow to the specified edges of either its path or its bounds
public class InnerShadowLayer: CALayer {
    private let shadow: Shadow
    private let edge: UIRectEdge

    public init(shadow: Shadow, edge: UIRectEdge) {
        self.shadow = shadow
        self.edge = edge
        super.init()
        setupShadow()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func layoutSublayers() {
        updateShadow()
    }

    private func setupShadow() {
        shadowColor = shadow.color.cgColor
        shadowOpacity = Float(shadow.opacity)
        shadowRadius = shadow.blur / 2.0
        masksToBounds = true
    }

    private func updateShadow() {
        shadowOffset = {
            let topWidth: CGFloat = 0
            let leftWidth = edge.contains(.left) ? shadow.y / 2 : 0
            let bottomWidth: CGFloat = 0
            let rightWidth = edge.contains(.right) ? -shadow.y / 2 : 0

            let topHeight = edge.contains(.top) ? shadow.y / 2 : 0
            let leftHeight: CGFloat = 0
            let bottomHeight = edge.contains(.bottom) ? -shadow.y / 2 : 0
            let rightHeight: CGFloat = 0

            return CGSize(width: [topWidth, leftWidth, bottomWidth, rightWidth].reduce(0, +),
                          height: [topHeight, leftHeight, bottomHeight, rightHeight].reduce(0, +))
        }()

        let insets = UIEdgeInsets(top: edge.contains(.top) ? -bounds.height : 0,
                                  left: edge.contains(.left) ? -bounds.width : 0,
                                  bottom: edge.contains(.bottom) ? -bounds.height : 0,
                                  right: edge.contains(.right) ? -bounds.width : 0)
        let path = UIBezierPath(rect: bounds.inset(by: insets))
        let cutout = UIBezierPath(rect: bounds).reversing()
        path.append(cutout)
        shadowPath = path.cgPath
    }
}

1

kode ini berhasil untuk saya

class InnerDropShadowView: UIView {
    override func draw(_ rect: CGRect) {
        //Drawing code
        let context = UIGraphicsGetCurrentContext()
        //// Shadow Declarations
        let shadow: UIColor? = UIColor.init(hexString: "a3a3a3", alpha: 1.0) //UIColor.black.withAlphaComponent(0.6) //UIColor.init(hexString: "d7d7da", alpha: 1.0)
        let shadowOffset = CGSize(width: 0, height: 0)
        let shadowBlurRadius: CGFloat = 7.5
        //// Rectangle Drawing
        let rectanglePath = UIBezierPath(rect: bounds)
        UIColor.groupTableViewBackground.setFill()
        rectanglePath.fill()
        ////// Rectangle Inner Shadow
        context?.saveGState()
        UIRectClip(rectanglePath.bounds)
        context?.setShadow(offset: CGSize.zero, blur: 0, color: nil)
        context?.setAlpha((shadow?.cgColor.alpha)!)
        context?.beginTransparencyLayer(auxiliaryInfo: nil)
        do {
            let opaqueShadow: UIColor? = shadow?.withAlphaComponent(1)
            context?.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: opaqueShadow?.cgColor)
            context!.setBlendMode(.sourceOut)
            context?.beginTransparencyLayer(auxiliaryInfo: nil)
            opaqueShadow?.setFill()
            rectanglePath.fill()
            context!.endTransparencyLayer()
        }
        context!.endTransparencyLayer()
        context?.restoreGState()
    }
}

0

Ada beberapa kode di sini yang dapat melakukan ini untuk Anda. Jika Anda mengubah lapisan dalam tampilan Anda (dengan menimpa + (Class)layerClass), ke JTAInnerShadowLayer maka Anda dapat mengatur bayangan dalam pada lapisan indentasi di metode init Anda dan itu akan melakukan pekerjaan untuk Anda. Jika Anda juga ingin menggambar konten asli, pastikan Anda memanggil setDrawOriginalImage:yeslapisan indentasi. Ada entri blog tentang cara kerjanya di sini .


@MiteshDobareeya Baru saja menguji kedua tautan tersebut dan tampaknya berfungsi dengan baik (termasuk di tab pribadi). Tautan mana yang menyebabkan masalah Anda?
James Snook

Bisakah Anda melihat implementasi kode bayangan dalam ini. Ini hanya bekerja dalam metode ViewDidAppear. Dan Menunjukkan beberapa kedipan. drive.google.com/open?id=1VtCt7UFYteq4UteT0RoFRjMfFnbibD0E
Mitesh Dobareeya

0

Menggunakan Gradient Layer:

UIView * mapCover = [UIView new];
mapCover.frame = map.frame;
[view addSubview:mapCover];

CAGradientLayer * vertical = [CAGradientLayer layer];
vertical.frame = mapCover.bounds;
vertical.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
                        (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                        (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                        (id)[UIColor whiteColor].CGColor, nil];
vertical.locations = @[@0.01,@0.1,@0.9,@0.99];
[mapCover.layer insertSublayer:vertical atIndex:0];

CAGradientLayer * horizontal = [CAGradientLayer layer];
horizontal.frame = mapCover.bounds;
horizontal.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
                     (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                     (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                     (id)[UIColor whiteColor].CGColor, nil];
horizontal.locations = @[@0.01,@0.1,@0.9,@0.99];
horizontal.startPoint = CGPointMake(0.0, 0.5);
horizontal.endPoint = CGPointMake(1.0, 0.5);
[mapCover.layer insertSublayer:horizontal atIndex:0];

0

Ada solusi sederhana - cukup gambar bayangan normal dan putar, seperti ini

@objc func shadowView() -> UIView {
        let shadowView = UIView(frame: .zero)
        shadowView.backgroundColor = .white
        shadowView.layer.shadowColor = UIColor.grey.cgColor
        shadowView.layer.shadowOffset = CGSize(width: 0, height: 2)
        shadowView.layer.shadowOpacity = 1.0
        shadowView.layer.shadowRadius = 4
        shadowView.layer.compositingFilter = "multiplyBlendMode"
        return shadowView
    }

func idtm_addBottomShadow() {
        let shadow = shadowView()
        shadow.transform = transform.rotated(by: 180 * CGFloat(Double.pi))
        shadow.transform = transform.rotated(by: -1 * CGFloat(Double.pi))
        shadow.translatesAutoresizingMaskIntoConstraints = false
        addSubview(shadow)
        NSLayoutConstraint.activate([
            shadow.leadingAnchor.constraint(equalTo: leadingAnchor),
            shadow.trailingAnchor.constraint(equalTo: trailingAnchor),
            shadow.bottomAnchor.constraint(equalTo: bottomAnchor),
            shadow.heightAnchor.constraint(equalToConstant: 1),
            ])
    }
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.