Effetto ombra interna sul livello UIView?


92

Ho il seguente 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];

Mi piacerebbe aggiungere un effetto ombra interna , ma non sono abbastanza sicuro di come farlo. Suppongo che mi sarebbe stato richiesto di disegnare in drawRect, tuttavia questo aggiungerebbe il livello sopra altri oggetti UIView, poiché dovrebbe essere una barra dietro alcuni pulsanti, quindi sono perplesso su cosa fare?

Potrei aggiungere un altro livello, ma ancora una volta, non sono sicuro di come ottenere l'effetto ombra interna (come questo:

inserisci qui la descrizione dell'immagine

Aiuto apprezzato ...

Risposte:


108

Per chiunque altro si chieda come disegnare un'ombra interna utilizzando Core Graphics secondo il suggerimento di Costique, ecco come: (su iOS regolare secondo necessità)

Nel tuo drawRect: metodo ...

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

Quindi, essenzialmente ci sono i seguenti passaggi:

  1. Crea il tuo percorso
  2. Imposta il colore di riempimento che desideri, aggiungi questo percorso al contesto e riempi il contesto
  3. Ora crea un rettangolo più grande che possa delimitare il percorso visibile. Prima di chiudere questo percorso, aggiungi il percorso visibile. Quindi chiudere il percorso, in modo da creare una forma con il percorso visibile sottratto. Potresti voler esaminare i metodi di riempimento (avvolgimento diverso da zero di pari / dispari) a seconda di come hai creato questi percorsi. In sostanza, per far "sottrarre" i sottopercorsi quando vengono sommati, è necessario disegnarli (o meglio costruirli) in direzioni opposte, una in senso orario e l'altra in senso antiorario.
  4. Quindi è necessario impostare il percorso visibile come tracciato di ritaglio nel contesto, in modo da non disegnare nulla al di fuori di esso sullo schermo.
  5. Quindi imposta l'ombra sul contesto, che include l'offset, la sfocatura e il colore.
  6. Quindi riempire la forma grande con il buco al suo interno. Il colore non ha importanza, perché se hai fatto tutto bene, non vedrai questo colore, solo l'ombra.

Grazie, ma è possibile regolare il raggio? Attualmente è basato sui limiti, ma mi piacerebbe basarmi su un raggio impostato (come 5.0f). Con il codice sopra, è arrotondato troppo.
runmad

2
@runmad Bene, puoi creare qualsiasi tipo di CGPath visibile che desideri, l'esempio usato qui è proprio questo, un esempio, scelto per brevità. Se desideri creare un rettangolo arrotondato, puoi semplicemente fare qualcosa come: CGPath visiblePath = [UIBezierPath bezierPathWithRoundedRect: rect cornerRadius: radius] .CGPath Spero che aiuti.
Daniel Thorpe

4
@DanielThorpe: +1 per la bella risposta. Ho corretto il codice del percorso rettangolo arrotondato (il tuo si è rotto durante la modifica del raggio) e ho semplificato il codice del percorso rettangolo esterno. Spero non ti dispiaccia.
Regexident

Come posso impostare correttamente l'ombra interna da 4 direzioni, non solo 2?
Protocollo

@Protocole puoi impostare l'offset su {0,0}, ma usa un raggio dell'ombra, ad esempio, 4.f.
Daniel Thorpe

47

So di essere in ritardo a questa festa, ma questo mi avrebbe aiutato a trovare presto nei miei viaggi ...

Per dare credito dove è dovuto, questa è essenzialmente una modifica dell'elaborazione di Daniel Thorpe sulla soluzione di Costique di sottrarre una regione più piccola da una regione più grande. Questa versione è per coloro che utilizzano la composizione dei livelli anziché l'override-drawRect:

La CAShapeLayerclasse può essere utilizzata per ottenere lo stesso effetto:

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

A questo punto, se il tuo livello genitore non maschera i suoi limiti, vedrai l'area extra del livello maschera attorno ai bordi del livello. Saranno 42 pixel di nero se hai copiato direttamente l'esempio. Per sbarazzartene, puoi semplicemente usarne un altro CAShapeLayercon lo stesso percorso e impostarlo come maschera del livello ombra:

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

Non l'ho confrontato personalmente, ma sospetto che l'utilizzo di questo approccio insieme alla rasterizzazione sia più performante dell'override -drawRect:.


3
someInnerPath? Potresti spiegarlo ancora un po 'per favore.
Moe

4
@Moe Può essere qualsiasi CGPath arbitrario che desideri. [[UIBezierPath pathWithRect:[shadowLayer bounds]] CGPath]essendo la scelta più semplice.
Matt Wilding

Grazie per questo Matt :-)
Moe

Ricevo un rettangolo nero (esterno) per shadowLayer.path che disegna correttamente l'ombra interna. Come posso eliminarlo (il rettangolo esterno nero)? Sembra che tu possa impostare fillColor solo all'interno di un contesto e non ne usi uno.
Olivier

11
Funziona molto bene! Ho caricato su GitHub con alcune aggiunte. Provate :) github.com/inamiy/YIInnerShadowView
inamiy

35

È possibile disegnare un'ombra interna con Core Graphics creando un percorso rettangolo grande fuori dai limiti, sottraendo un tracciato rettangolo delle dimensioni di un limite e riempiendo il percorso risultante con un'ombra "normale".

Tuttavia, poiché è necessario combinarlo con un livello sfumato, penso che una soluzione più semplice sia creare un'immagine PNG trasparente in 9 parti dell'ombra interna e allungarla alla giusta dimensione. L'immagine dell'ombra in 9 parti sarebbe simile a questa (la sua dimensione è 21x21 pixel):

testo alternativo

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

Quindi imposta la cornice di innerShadowLayer e dovrebbe allungare l'ombra correttamente.


Sì, suppongo tu abbia ragione. Volevo solo che lo strato fosse il più piatto possibile. Potrei creare l'immagine in Photoshop con l'ombra interna e l'aspetto sfumato, ho solo problemi con i colori che corrispondono al 100% sul dispositivo quando utilizzo un'immagine.
runmad

Sì, questo è un problema con tutte le sfumature e le ombre, non riesco a riprodurre questi effetti di Photoshop 1: 1 su iOS, per quanto ci provi.
Costique

29

Una versione semplificata che utilizza solo un CALayer, in 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)
    }
}

Nota che il livello innerShadow non dovrebbe avere un colore di sfondo opaco poiché verrà renderizzato davanti all'ombra.


L'ultima riga contiene "layer". Da dove viene questo?
Charlie Seligman

@CharlieSeligman È il livello genitore, che potrebbe essere qualsiasi livello. È possibile utilizzare un livello personalizzato o il livello della vista (UIView ha una proprietà del livello).
Patrick Pijnappel

dovrebbe essere let innerShadow = CALayer(); innerShadow.frame = bounds. Senza i giusti limiti non disegnerebbe l'ombra giusta. Grazie comunque
haik.ampardjian

@noir_eagle Vero, anche se probabilmente vorrai impostarlo layoutSubviews()per mantenerlo sincronizzato
Patrick Pijnappel

Destra! O in layoutSubviews()o indraw(_ rect)
haik.ampardjian

24

Un po 'complicato, ma evita di dover usare immagini (leggi: facile cambiare i colori, il raggio dell'ombra, ecc.) Ed è solo poche righe di codice.

  1. Aggiungi un UIImageView come prima sottoview della UIView su cui vuoi il dropshadow. Io uso IB, ma puoi fare lo stesso a livello di programmazione.

  2. Supponendo che il riferimento a UIImageView sia "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];

Avvertenza: devi avere un bordo, altrimenti l'ombra non viene visualizzata. [UIColor clearColor] non funziona. Nell'esempio uso un colore diverso, ma puoi modificarlo per ottenere lo stesso colore dell'inizio dell'ombra. :)

Vedi il commento di bbrame di seguito sulla UIColorFromRGBmacro.


L'ho lasciato fuori, ma presumo che lo faresti come parte dell'aggiunta della visualizzazione dell'immagine: assicurati di impostare la cornice sullo stesso rettangolo del genitore UIView. Se stai usando IB, imposta i montanti e le molle a destra per avere la dimensione dell'ombra con la vista se cambierai la cornice della vista principale. Nel codice dovrebbe esserci una maschera di ridimensionamento che puoi OPPURE fare lo stesso, AFAIK.
jinglesthula

Questo è il modo più semplice ora, ma tieni presente che i metodi shadow CALayer sono disponibili solo in iOS 3.2 e versioni successive. Supporto 3.1, quindi circondo l'impostazione di questi attributi in un if ([layer respondsToSelector: @selector (setShadowColor :)]) {
DougW

Questo non sembra funzionare per me. Almeno su xcode 4.2 e ios simulator 4.3. Per far apparire l'ombra devo aggiungere un colore di sfondo ... a quel punto l'ombretto appare solo all'esterno.
Andrea

@Andrea - tieni presente l'avvertenza che ho menzionato sopra. Penso che un colore di sfondo o un bordo potrebbero avere lo stesso effetto di "dargli qualcosa a cui aggiungere l'ombra". Per quanto riguarda l'aspetto esterno, se UIImageView non è una sottoview di quella su cui vuoi l'ombra interna, potrebbe essere - dovrei guardare il tuo codice per vederlo.
jinglesthula

Solo per correggere la mia precedente affermazione ... il codice funziona davvero ... mi mancava qualcosa ma sfortunatamente non riesco a ricordarlo in questo momento. :) Quindi ... grazie per aver condiviso questo snippet di codice.
Andrea

17

Meglio tardi che mai...

Ecco un altro approccio, probabilmente non migliore di quelli già pubblicati, ma è carino e semplice -

-(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 è possibile impostare l'ombra solo con un lato specifico? Come solo in alto o in alto / in basso o in alto / a destra ecc.
Mitesh Dobareeya

8

Invece di disegnare l'ombra interna tramite drawRect o aggiungi UIView alla vista. Puoi aggiungere direttamente CALayer al bordo, ad esempio: se voglio l'effetto ombra interna sul fondo di 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];

Questo aggiunge un'ombra interna inferiore per UIView di destinazione


6

Ecco una versione di swift, change startPointe endPointper farlo su ogni lato.

        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)

Ha funzionato per me !! Grazie.
iUser

5

Questa è la tua soluzione, che ho esportato da 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

Sono molto in ritardo per la festa ma vorrei restituire alla comunità .. Questo è un metodo che ho scritto per rimuovere l'immagine di sfondo di UITextField poiché fornivo una libreria statica e NESSUNA risorsa ... l'ho usato per una schermata di immissione del PIN di quattro istanze di UITextField che potrebbero visualizzare un carattere grezzo o (BOOL) [self isUsingBullets] o (BOOL) [self usingAsterisks] in ViewController. L'app è per iPhone / iPhone retina / iPad / iPad Retina quindi non devo fornire quattro immagini ...

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

Spero che questo aiuti qualcuno poiché questo forum mi ha aiutato.


3

Controlla il fantastico articolo di Inner Shadows in Quartz di Chris Emery che spiega come le ombre interne vengono disegnate da PaintCode e fornisce uno snippet di codice pulito e ordinato:

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

Ecco la mia soluzione in Swift 4.2. Vorresti provare?

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

Cosa succede se non lo sto usando come una classe separata, ma come usare nel mio codice, il contesto (ctx) è nullo quando ottengo questo:let ctx = UIGraphicsGetCurrentContext
Mohsin Khubaib Ahmed

@MohsinKhubaibAhmed Puoi ottenere il contesto corrente tramite il metodo UIGraphicsGetCurrentContext per recuperare quando alcune viste inseriscono il loro contesto nello stack.
Arco

@Arco Ho avuto qualche problema quando ho ruotato il dispositivo. Ho aggiunto "override convenience init (layer: Any) {self.init ()}". Ora nessun errore visualizzato!
Yuma Technical Inc.

Aggiunto init (layer: Any) per correggere il crash.
Nik Kov

2

Soluzione scalabile utilizzando CALayer in Swift

Con il descritto InnerShadowLayer puoi anche abilitare le ombre interne solo per bordi specifici, escludendone altri. (ad esempio, puoi abilitare le ombre interne solo sui bordi sinistro e superiore della tua vista)

È quindi possibile aggiungere un InnerShadowLayeralla visualizzazione utilizzando:

init(...) {

    // ... your initialization code ...

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

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

InnerShadowLayer implementazione

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

questo codice ha funzionato per me

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

C'è del codice qui che può farlo per te. Se cambi il livello nella tua vista (sovrascrivendo + (Class)layerClass), in JTAInnerShadowLayer, puoi impostare l'ombra interna sul livello di rientro nel tuo metodo init e farà il lavoro per te. Se vuoi disegnare anche il contenuto originale assicurati di richiamare setDrawOriginalImage:yesil livello di rientro. C'è un post sul blog su come funziona qui .


@MiteshDobareeya ha appena testato entrambi i collegamenti e sembrano funzionare bene (anche in una scheda privata). Quale collegamento ti stava causando problemi?
James Snook

Puoi guardare questa implementazione del codice shadow interno. Funziona solo nel metodo ViewDidAppear. E mostra un po 'di sfarfallio. drive.google.com/open?id=1VtCt7UFYteq4UteT0RoFRjMfFnbibD0E
Mitesh Dobareeya

0

Utilizzo del livello sfumato:

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

C'è una soluzione semplice: basta disegnare l'ombra normale e ruotare, in questo modo

@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),
            ])
    }
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.