Wewnętrzny efekt cienia na warstwie UIView?


92

Mam następujący CALayer:

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

Chciałbym dodać do tego efekt wewnętrznego cienia , ale nie jestem pewien, jak to zrobić. Przypuszczam, że musiałbym rysować w drawRect, ale dodałoby to warstwę na wierzchu innych obiektów UIView, ponieważ ma to być pasek za niektórymi przyciskami, więc nie wiem, co robić?

Mógłbym dodać kolejną warstwę, ale znowu nie jestem pewien, jak uzyskać efekt wewnętrznego cienia (na przykład:

wprowadź opis obrazu tutaj

Doceniona pomoc ...

Odpowiedzi:


108

Dla każdego, kto zastanawia się, jak narysować wewnętrzny cień za pomocą Core Graphics zgodnie z sugestią Costique, oto jak: (na iOS dostosuj w razie potrzeby)

W twoim drawRect: metoda ...

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

Tak więc zasadniczo są następujące kroki:

  1. Stwórz swoją ścieżkę
  2. Ustaw żądany kolor wypełnienia, dodaj tę ścieżkę do kontekstu i wypełnij kontekst
  3. Teraz utwórz większy prostokąt, który może wiązać widoczną ścieżkę. Przed zamknięciem tej ścieżki dodaj widoczną ścieżkę. Następnie zamknij ścieżkę, aby utworzyć kształt z odjętą widoczną ścieżką. Możesz chcieć zbadać metody wypełniania (niezerowe nawijanie parzystych / nieparzystych) w zależności od tego, jak utworzyłeś te ścieżki. Zasadniczo, aby ścieżki podrzędne „odejmowały” po dodaniu ich do siebie, należy je narysować (a raczej skonstruować) w przeciwnych kierunkach, jedną zgodnie z ruchem wskazówek zegara, a drugą przeciwnie do ruchu wskazówek zegara.
  4. Następnie musisz ustawić widoczną ścieżkę jako ścieżkę przycinającą w kontekście, aby nie rysować niczego poza nią na ekranie.
  5. Następnie ustaw cień w kontekście, który obejmuje przesunięcie, rozmycie i kolor.
  6. Następnie wypełnij duży kształt otworem w nim. Kolor nie ma znaczenia, ponieważ jeśli zrobiłeś wszystko dobrze, nie zobaczysz tego koloru, tylko cień.

Dzięki, ale czy można dopasować promień? Obecnie opiera się na granicach, ale zamiast tego chciałbym opierać się na ustawionym promieniu (np. 5.0f). W powyższym kodzie jest za bardzo zaokrąglony.
runmad

2
@runmad Cóż, możesz utworzyć dowolną widoczną ścieżkę CGPath, którą chcesz, przykład użyty tutaj jest po prostu przykładem, wybranym ze względu na zwięzłość. Jeśli chcesz utworzyć zaokrąglony prostokąt, możesz po prostu zrobić coś takiego: CGPath visiblePath = [UIBezierPath bezierPathWithRoundedRect: rect cornerRadius: radius] .CGPath Mam nadzieję, że to pomoże.
Daniel Thorpe

4
@DanielThorpe: +1 za miłą odpowiedź. Poprawiłem zaokrąglony kod ścieżki prostokątnej (twój się zepsuł przy zmianie promienia) i uprościłem kod zewnętrznej ścieżki prostokątnej. Mam nadzieję, że nie masz nic przeciwko.
Regexident

Jak prawidłowo ustawić wewnętrzny cień z 4 kierunków, a nie tylko z 2?
Protocole

@Protocole możesz ustawić przesunięcie na {0,0}, ale użyj promienia cienia, powiedzmy 4.f.
Daniel Thorpe,

47

Wiem, że spóźniłem się na to przyjęcie, ale pomogłoby mi to znaleźć na początku moich podróży ...

By przyznać kredyt tam, gdzie należy się kredyt, jest to zasadniczo modyfikacja opracowania Daniela Thorpe'a na temat rozwiązania Costique polegającego na odejmowaniu mniejszego regionu od większego. Ta wersja jest przeznaczona dla osób używających kompozycji warstw zamiast nadpisywania-drawRect:

CAShapeLayerKlasy mogą być wykorzystane do osiągnięcia tego samego efektu:

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

W tym momencie, jeśli twoja warstwa nadrzędna nie maskuje do granic, zobaczysz dodatkowy obszar warstwy maski wokół krawędzi warstwy. Będzie to 42 piksele czerni, jeśli bezpośrednio skopiowałeś przykład. Aby się go pozbyć, możesz po prostu użyć innej CAShapeLayerz tą samą ścieżką i ustawić ją jako maskę warstwy cienia:

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

Sam tego nie testowałem, ale podejrzewam, że użycie tego podejścia w połączeniu z rasteryzacją jest bardziej wydajne niż nadpisywanie -drawRect:.


3
someInnerPath? Czy mógłbyś to trochę bardziej wyjaśnić?
Moe,

4
@Moe Może to być dowolna ścieżka CGPath. [[UIBezierPath pathWithRect:[shadowLayer bounds]] CGPath]jest najprostszym wyborem.
Matt Wilding,

Pozdrawiam Matt :-)
Moe

Otrzymuję czarny (zewnętrzny) prostokąt dla shadowLayer.path, który poprawnie rysuje wewnętrzny cień. Jak się go pozbyć (czarny prostokąt zewnętrzny)? Wygląda na to, że możesz ustawić fillColor tylko w kontekście i nie używasz go.
Olivier

11
To działa bardzo ładnie! Wgrałem na github z kilkoma dodatkami. Spróbuj :) github.com/inamiy/YIInnerShadowView
inamiy

35

Możliwe jest narysowanie wewnętrznego cienia za pomocą Core Graphics, tworząc dużą prostokątną ścieżkę poza granicami, odejmując prostokątną ścieżkę o rozmiarze granic i wypełniając wynikową ścieżkę „normalnym” cieniem.

Ponieważ jednak musisz połączyć go z warstwą gradientową, myślę, że łatwiejszym rozwiązaniem jest utworzenie 9-częściowego przezroczystego obrazu PNG wewnętrznego cienia i rozciągnięcie go do odpowiedniego rozmiaru. 9-częściowy obraz w cieniu wyglądałby następująco (jego rozmiar to 21x21 pikseli):

tekst alternatywny

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

Następnie ustaw oprawkę innerShadowLayer, która powinna odpowiednio rozciągnąć cień.


Tak, przypuszczam, że masz rację. Po prostu chciałem, aby warstwa była jak najbardziej płaska. Mogłem stworzyć obraz w Photoshopie z wewnętrznym cieniem i wyglądem gradientu, po prostu mam problemy z dopasowaniem kolorów w 100% na urządzeniu podczas używania obrazu.
runmad

Tak, to problem ze wszystkimi gradientami i cieniami, po prostu nie mogę odtworzyć tych efektów Photoshopa 1: 1 na iOS, ciężko, gdy próbuję.
Costique

31

Uproszczona wersja wykorzystująca tylko CALayer, w 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)
    }
}

Zwróć uwagę, że warstwa innerShadow nie powinna mieć nieprzezroczystego koloru tła, ponieważ zostanie on wyrenderowany przed cieniem.


Ostatnia linia zawiera „warstwę”. Skąd to pochodzi?
Charlie Seligman

@CharlieSeligman To warstwa nadrzędna, która może być dowolną warstwą. Możesz użyć warstwy niestandardowej lub warstwy widoku (UIView ma właściwość warstwy).
Patrick Pijnappel

powinno być let innerShadow = CALayer(); innerShadow.frame = bounds. Bez odpowiednich granic nie rysowałby właściwego cienia. W każdym razie dzięki
haik.ampardjian

@noir_eagle Prawda, chociaż prawdopodobnie chcesz to ustawić, layoutSubviews()aby zachować synchronizację
Patrick Pijnappel

Dobrze! Albo w layoutSubviews()lub wdraw(_ rect)
haik.ampardjian

24

Trochę okrężny sposób, ale pozwala uniknąć używania obrazów (czytaj: łatwe do zmiany kolorów, promienia cienia itp.) I to tylko kilka wierszy kodu.

  1. Dodaj UIImageView jako pierwszy widok podrzędny UIView, na którym chcesz umieścić cień. Używam IB, ale możesz zrobić to samo programowo.

  2. Zakładając, że odwołanie do UIImageView to „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];

Uwaga: musisz mieć obramowanie, inaczej cień się nie pojawi. [UIColor clearColor] nie działa. W przykładzie używam innego koloru, ale możesz z nim zadzierać, aby miał ten sam kolor, co początek cienia. :)

Zobacz komentarz bbrame poniżej dotyczący UIColorFromRGBmakra.


Pominąłem to, ale załóżmy, że zrobisz to w ramach dodawania widoku obrazu - upewnij się, że ustawiłeś ramkę na ten sam prostokąt, co nadrzędny UIView. Jeśli używasz IB, ustaw rozpórki i sprężyny odpowiednio, aby mieć rozmiar cienia w widoku, jeśli będziesz zmieniać ramkę widoku nadrzędnego. W kodzie powinna znajdować się maska ​​zmiany rozmiaru, którą możesz LUB zrobić to samo, AFAIK.
jinglesthula

Jest to teraz najłatwiejszy sposób, ale należy pamiętać, że metody cienia CALayer są dostępne tylko w iOS 3.2 i nowszych. Obsługuję 3.1, więc otaczam ustawienie tych atrybutów w if ([layer respondsToSelector: @selector (setShadowColor :)]) {
DougW

To chyba nie działa dla mnie. Przynajmniej na Xcode 4.2 i symulatorze iOS 4.3. Aby cień pojawił się, muszę dodać kolor tła ... w tym momencie cień pojawia się tylko na zewnątrz.
Andrea,

@Andrea - pamiętaj o zastrzeżeniu, o którym wspomniałem powyżej. Myślę, że kolor tła lub obramowanie mogą mieć ten sam efekt, „dając mu coś, do czego można dodać cień”. Jeśli chodzi o pojawianie się na zewnątrz, jeśli UIImageView nie jest podwidokiem tego, na którym chcesz mieć wewnętrzny cień, może to być to - musiałbym spojrzeć na twój kod, aby zobaczyć.
jinglesthula

Żeby skorygować moje poprzednie stwierdzenie ... kod faktycznie działa ... czegoś mi brakowało, ale niestety nie mogę tego teraz sobie przypomnieć. :) Więc ... dziękuję za udostępnienie tego fragmentu kodu.
Andrea,

17

Lepiej późno niż wcale...

Oto inne podejście, prawdopodobnie nie lepsze niż te już opublikowane, ale jest ładne i proste -

-(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 czy można ustawić cień tylko z określonej strony? Jak tylko na górze lub na górze / na dole lub na górze / po prawej itd.
Mitesh Dobareeya

8

Zamiast rysować wewnętrzny cień przez drawRect lub dodać UIView do widoku. Możesz bezpośrednio dodać CALayer do obramowania, na przykład: jeśli chcę uzyskać efekt wewnętrznego cienia na dnie 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];

Spowoduje to dodanie dolnego wewnętrznego cienia dla docelowego UIView


6

Oto wersja szybka, zmiana startPointi endPointzrobienie tego z każdej strony.

        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)

Pracował dla mnie !! Dziękuję Ci.
iUser

5

Oto Twoje rozwiązanie, które wyeksportowałem z 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

Jestem bardzo spóźniony na imprezę, ale chciałbym oddać to społeczności. To jest metoda, którą napisałem, aby usunąć obraz tła UITextField, ponieważ dostarczałem bibliotekę statyczną i BEZ zasobów ... Użyłem tego do ekran wprowadzania kodu PIN zawierający cztery instancje UITextField, które mogą wyświetlać jeden znak surowy lub (BOOL) [self isUsingBullets] lub (BOOL) [self usingAsterisks] w programie ViewController. Aplikacja jest przeznaczona na iPhone / iPhone Retina / iPad / iPad Retina, więc nie muszę dostarczać czterech obrazów ...

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

Mam nadzieję, że to komuś pomogło, ponieważ to forum pomogło mi.


3

Sprawdź świetny artykuł Chrisa Emery'ego z Inner Shadows in Quartz , który wyjaśnia, w jaki sposób cienie wewnętrzne są rysowane przez PaintCode i podaje czysty i schludny fragment kodu:

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

Oto moje rozwiązanie w Swift 4.2. Chcesz spróbować?

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

Co jeśli nie używam go jako oddzielnej klasy, ale jak w moim kodzie, kontekst (ctx) jest zerowy, gdy otrzymam to:let ctx = UIGraphicsGetCurrentContext
Mohsin Khubaib Ahmed

@MohsinKhubaibAhmed Możesz pobrać bieżący kontekst za pomocą metody, UIGraphicsGetCurrentContext aby pobrać, gdy niektóre widoki umieszczają swój kontekst na stosie.
Arco,

@Arco Miałem kłopoty po obróceniu urządzenia. Dodałem „zastąpienie wygody init (warstwa: Any) {self.init ()}”. Teraz nie jest wyświetlany żaden błąd!
Yuma Technical Inc.,

Dodano init (warstwa: Any), aby naprawić awarię.
Nik Kov

2

Skalowalne rozwiązanie wykorzystujące CALayer w Swift

Z opisanym InnerShadowLayer możesz również włączyć wewnętrzne cienie tylko dla określonych krawędzi, z wyłączeniem innych. (np. możesz włączyć wewnętrzne cienie tylko na lewej i górnej krawędzi widoku)

Następnie możesz dodać InnerShadowLayerdo widoku za pomocą:

init(...) {

    // ... your initialization code ...

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

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

InnerShadowLayer realizacja

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

ten kod zadziałał dla mnie

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

Jest tutaj kod , który może to dla Ciebie zrobić. Jeśli zmienisz warstwę w swoim widoku (przez nadpisanie + (Class)layerClass), na JTAInnerShadowLayer, możesz ustawić wewnętrzny cień na warstwie wcięcia w górę w swojej metodzie init i wykona pracę za Ciebie. Jeśli chcesz również narysować oryginalną zawartość, pamiętaj, aby wywołać setDrawOriginalImage:yeswarstwę wcięć. Jest blogu o tym, jak to działa tutaj .


@MiteshDobareeya Właśnie przetestowałem oba linki i wydaje się, że działają dobrze (w tym na prywatnej karcie). Który link powodował problemy?
James Snook

Czy możesz spojrzeć na tę implementację wewnętrznego kodu cienia. Działa tylko w metodzie ViewDidAppear. I pokazuje migotanie. drive.google.com/open?id=1VtCt7UFYteq4UteT0RoFRjMfFnbibD0E
Mitesh Dobareeya

0

Korzystanie z warstwy gradientu:

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

Jest proste rozwiązanie - po prostu narysuj zwykły cień i obróć w ten sposób

@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),
            ])
    }
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.