Innerer Schatteneffekt auf der UIView-Ebene?

92

Ich habe folgenden 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];

Ich möchte einen inneren Schatteneffekt hinzufügen , bin mir aber nicht ganz sicher, wie ich das machen soll. Ich nehme an, ich müsste in drawRect zeichnen, dies würde jedoch die Ebene über anderen UIView-Objekten hinzufügen, da es sich um eine Leiste hinter einigen Schaltflächen handeln soll, sodass ich nicht weiß, was ich tun soll.

Ich könnte eine weitere Ebene hinzufügen, bin mir aber nicht sicher, wie ich den inneren Schatteneffekt erzielen soll (wie folgt:

Geben Sie hier die Bildbeschreibung ein

Hilfe geschätzt ...

runmad
quelle

Antworten:

108

Für alle anderen, die sich fragen, wie sie mit Core Graphics gemäß Costique-Vorschlag einen inneren Schatten zeichnen sollen, gilt Folgendes: (unter iOS nach Bedarf anpassen)

In Ihrer drawRect: Methode ...

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

Im Wesentlichen gibt es also die folgenden Schritte:

  1. Erstellen Sie Ihren Weg
  2. Stellen Sie die gewünschte Füllfarbe ein, fügen Sie diesen Pfad dem Kontext hinzu und füllen Sie den Kontext
  3. Erstellen Sie nun ein größeres Rechteck, das den sichtbaren Pfad begrenzen kann. Fügen Sie den sichtbaren Pfad hinzu, bevor Sie diesen Pfad schließen. Schließen Sie dann den Pfad, sodass Sie eine Form erstellen, von der der sichtbare Pfad abgezogen wird. Abhängig davon, wie Sie diese Pfade erstellt haben, möchten Sie möglicherweise die Füllmethoden untersuchen (Wicklung ungerade ungerade / ungerade). Um die Unterpfade beim Addieren zu "subtrahieren", müssen Sie sie im Wesentlichen im Uhrzeigersinn und gegen den Uhrzeigersinn in entgegengesetzte Richtungen zeichnen (oder vielmehr konstruieren).
  4. Dann müssen Sie Ihren sichtbaren Pfad als Beschneidungspfad für den Kontext festlegen, damit Sie nichts außerhalb davon auf den Bildschirm zeichnen.
  5. Richten Sie dann den Schatten im Kontext ein, der den Versatz, die Unschärfe und die Farbe enthält.
  6. Dann füllen Sie die große Form mit dem Loch darin. Die Farbe spielt keine Rolle, denn wenn Sie alles richtig gemacht haben, sehen Sie diese Farbe nicht, nur den Schatten.
Daniel Thorpe
quelle
Danke, aber ist es möglich, den Radius anzupassen? Es basiert derzeit auf den Grenzen, aber ich möchte stattdessen auf einem festgelegten Radius basieren (wie 5.0f). Mit dem obigen Code ist es viel zu viel gerundet.
Runmad
2
@runmad Nun, Sie können jede Art von sichtbarem CGPath erstellen, die Sie möchten. Das hier verwendete Beispiel ist genau das, ein Beispiel, das der Kürze halber ausgewählt wurde. Wenn Sie ein abgerundetes Rechteck erstellen möchten, können Sie einfach Folgendes tun: CGPath sichtbarPath = [UIBezierPath bezierPathWithRoundedRect: RechteckeRadius: Radius] .CGPath Hoffe, das hilft.
Daniel Thorpe
4
@ DanielThorpe: +1 für die nette Antwort. Ich habe den gerundeten Rechteckpfadcode korrigiert (Ihr Code ist beim Ändern des Radius gebrochen) und den äußeren Rechteckpfadcode vereinfacht. Hoffe es macht dir nichts aus.
Regexident
Wie kann ich den inneren Schatten aus 4 Richtungen richtig einstellen, nicht nur aus 2?
Protocole
@Protocole Sie können den Versatz auf {0,0} setzen, aber einen Schattenradius von beispielsweise 4 verwenden.
Daniel Thorpe
47

Ich weiß, dass ich zu spät zu dieser Party komme, aber das hätte mir geholfen, früh auf meinen Reisen zu finden ...

Um Kredit zu geben, wo Kredit fällig ist, ist dies im Wesentlichen eine Modifikation von Daniel Thorpes Ausarbeitung von Costiques Lösung, eine kleinere Region von einer größeren Region zu subtrahieren. Diese Version ist für Benutzer gedacht, die die Ebenenzusammensetzung verwenden, anstatt sie zu überschreiben-drawRect:

Die CAShapeLayerKlasse kann verwendet werden, um den gleichen Effekt zu erzielen:

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

Wenn Ihre übergeordnete Ebene zu diesem Zeitpunkt nicht bis zu ihren Grenzen maskiert, wird an dieser Stelle der zusätzliche Bereich der Maskenebene um die Ränder der Ebene angezeigt. Dies sind 42 Pixel Schwarz, wenn Sie das Beispiel direkt kopiert haben. Um es loszuwerden, können Sie einfach ein anderes CAShapeLayermit demselben Pfad verwenden und es als Maske der Schattenebene festlegen:

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

Ich habe dies selbst nicht bewertet, aber ich vermute, dass die Verwendung dieses Ansatzes in Verbindung mit Rasterisierung leistungsfähiger ist als das Überschreiben -drawRect:.

Matt Wilding
quelle
3
someInnerPath? Könnten Sie das bitte etwas näher erläutern?
Moe
4
@Moe Es kann ein beliebiger beliebiger CGPath sein. [[UIBezierPath pathWithRect:[shadowLayer bounds]] CGPath]die einfachste Wahl sein.
Matt Wilding
Prost für diesen Matt :-)
Moe
Ich erhalte ein schwarzes (äußeres) Rechteck für den shadowLayer.path, das den inneren Schatten korrekt zeichnet. Wie kann ich es loswerden (das schwarze äußere Rechteck)? Sieht so aus, als könnten Sie die fillColor nur in einem Kontext festlegen und verwenden keinen.
Olivier
11
Das funktioniert sehr gut! Ich habe mit einigen Ergänzungen auf github hochgeladen.
Probieren
35

Es ist möglich, mit Core Graphics einen inneren Schatten zu zeichnen, indem Sie einen großen Rechteckpfad außerhalb der Grenzen erstellen, einen Rechteckpfad mit Grenzgröße subtrahieren und den resultierenden Pfad mit einem "normalen" Schatten füllen.

Da Sie es jedoch mit einer Verlaufsebene kombinieren müssen, besteht meiner Meinung nach eine einfachere Lösung darin, ein 9-teiliges transparentes PNG-Bild des inneren Schattens zu erstellen und es auf die richtige Größe zu strecken. Das 9-teilige Schattenbild würde folgendermaßen aussehen (seine Größe beträgt 21 x 21 Pixel):

Alt-Text

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

Stellen Sie dann den Rahmen von innerShadowLayer ein und er sollte den Schatten richtig dehnen.

Costique
quelle
Ja, ich nehme an, du hast recht. Ich wollte nur, dass die Schicht so flach wie möglich ist. Ich könnte das Bild in Photoshop mit dem inneren Schatten und dem Farbverlauf erstellen. Ich habe nur Probleme mit den Farben, die bei Verwendung eines Bildes zu 100% auf dem Gerät übereinstimmen.
Runmad
Ja, das ist ein Problem mit allen Verläufen und Schatten. Ich kann diese Photoshop-Effekte unter iOS einfach nicht 1: 1 reproduzieren, so sehr ich es auch versuche.
Costique
29

Eine vereinfachte Version mit nur einem 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)
    }
}

Beachten Sie, dass die innerShadow-Ebene keine undurchsichtige Hintergrundfarbe haben sollte, da diese vor dem Schatten gerendert wird.

Patrick Pijnappel
quelle
Die letzte Zeile enthält 'Ebene'. Woher kommt das?
Charlie Seligman
@CharlieSeligman Es ist die übergeordnete Ebene, die eine beliebige Ebene sein kann. Sie können eine benutzerdefinierte Ebene oder die Ebene der Ansicht verwenden (UIView verfügt über eine Ebeneneigenschaft).
Patrick Pijnappel
sollte sein let innerShadow = CALayer(); innerShadow.frame = bounds. Ohne die richtigen Grenzen würde es nicht den richtigen Schatten zeichnen.
Trotzdem
@noir_eagle True, obwohl Sie das wahrscheinlich einstellen möchten, layoutSubviews()um es synchron zu halten
Patrick Pijnappel
Richtig! Entweder in layoutSubviews()oder indraw(_ rect)
haik.ampardjian
24

Ein bisschen rundherum, aber es wird vermieden, Bilder verwenden zu müssen (lesen Sie: einfach zu ändernde Farben, Schattenradius usw.) und es sind nur ein paar Codezeilen.

  1. Fügen Sie eine UIImageView als erste Unteransicht der UIView hinzu, auf der der Dropshadow angezeigt werden soll. Ich benutze IB, aber Sie können das gleiche programmgesteuert tun.

  2. Angenommen, der Verweis auf UIImageView lautet "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];

Vorsichtsmaßnahme: Sie müssen einen Rand haben, sonst wird der Schatten nicht angezeigt. [UIColor clearColor] funktioniert nicht. Im Beispiel verwende ich eine andere Farbe, aber Sie können damit herumspielen, damit sie dieselbe Farbe wie der Anfang des Schattens hat. :) :)

Siehe den Kommentar von bbrame unten zum UIColorFromRGBMakro.

Jinglesthula
quelle
Ich habe es weggelassen, aber ich gehe davon aus, dass Sie dies als Teil des Hinzufügens der Bildansicht tun würden. Stellen Sie sicher, dass der Rahmen auf das gleiche Rechteck wie das übergeordnete UIView eingestellt ist. Wenn Sie IB verwenden, stellen Sie die Streben und Federn so ein, dass die Schattengröße mit der Ansicht übereinstimmt, wenn Sie den Rahmen der übergeordneten Ansicht ändern. Im Code sollte es eine Größenänderungsmaske geben, die Sie ODER verwenden können, AFAIK.
Jinglesthula
Dies ist jetzt der einfachste Weg. Beachten Sie jedoch, dass die CALayer-Schattenmethoden nur in iOS 3.2 und höher verfügbar sind. Ich unterstütze 3.1, also umsetze ich das Setzen dieser Attribute in einem if ([Layer antwortetToSelector: @selector (setShadowColor :)]) {
DougW
Das scheint bei mir nicht zu funktionieren. Zumindest auf xcode 4.2 und ios simulator 4.3. Um den Schatten erscheinen zu lassen, muss ich eine Hintergrundfarbe hinzufügen ... an diesem Punkt erscheint der Schlagschatten nur außerhalb.
Andrea
@Andrea - bedenken Sie die oben erwähnte Einschränkung. Ich denke, eine Hintergrundfarbe oder ein Rand könnte den gleichen Effekt haben, als würde man ihm etwas geben, dem man den Schatten hinzufügen kann. Wenn das UIImageView keine Unteransicht desjenigen ist, auf dem der innere Schatten angezeigt werden soll, muss es sich möglicherweise um Ihren Code handeln.
Jinglesthula
Nur um meine vorherige Aussage zu korrigieren ... der Code funktioniert tatsächlich ... Ich habe etwas vermisst, aber leider kann ich mich momentan nicht daran erinnern. :) Also ... danke, dass du dieses Code-Snippet geteilt hast.
Andrea
17

Besser spät als nie...

Hier ist ein anderer Ansatz, wahrscheinlich nicht besser als die bereits veröffentlichten, aber er ist nett und einfach -

-(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
quelle
@SomaMan ist es möglich, Schatten nur mit einer bestimmten Seite zu setzen? Wie nur oben oder oben / unten oder oben / rechts usw.
Mitesh Dobareeya
8

Anstatt mit drawRect einen inneren Schatten zu zeichnen oder der Ansicht UIView hinzuzufügen. Sie können CALayer direkt zum Rand hinzufügen, zum Beispiel: Wenn ich einen inneren Schatteneffekt auf der Unterseite von UIView V haben möchte.

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

Dadurch wird ein unterer innerer Schatten für das Ziel-UIView hinzugefügt

Jerry Zhang
quelle
6

Hier ist eine Version von schnell, ändern startPointund endPointauf jeder Seite zu machen.

        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)
William Hu
quelle
Hat für mich gearbeitet !! Danke dir.
Benutzer
5

Dies ist Ihre Lösung, die ich aus PaintCode exportiert habe :

-(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);
}
Dmitriy Kirakosyan
quelle
3

Ich bin sehr spät zur Party, aber ich möchte der Community etwas zurückgeben. Dies ist eine Methode, die ich geschrieben habe, um das UITextField-Hintergrundbild zu entfernen, da ich eine statische Bibliothek und KEINE Ressourcen bereitgestellt habe Ein PIN-Eingabebildschirm mit vier UITextField-Instanzen, auf denen in ViewController ein Zeichen roh oder (BOOL) [self isUsingBullets] oder (BOOL) [self usingAsterisks] angezeigt werden kann. App ist für iPhone / iPhone Retina / iPad / iPad Retina, so dass ich nicht vier Bilder liefern muss ...

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

Ich hoffe das hilft jemandem, denn dieses Forum hat mir geholfen.

Foxnolds
quelle
3

Lesen Sie den großartigen Artikel über innere Schatten in Quarz von Chris Emery , in dem erklärt wird, wie die inneren Schatten von PaintCode gezeichnet werden, und der einen sauberen und übersichtlichen Codeausschnitt enthält:

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

Hier ist meine Lösung in Swift 4.2. Möchten Sie es versuchen?

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)
      }
    } 
  }
}
Arco
quelle
Was ist, wenn ich es nicht als separate Klasse verwende, sondern wie in meinem Code, ist der Kontext (ctx) gleich Null, wenn ich let ctx = UIGraphicsGetCurrentContext
Folgendes
@MohsinKhubaibAhmed Sie können den aktuellen Kontext nach Methode abrufen UIGraphicsGetCurrentContext , wenn einige Ansichten ihren Kontext auf den Stapel verschieben.
Arco
@Arco Ich hatte einige Probleme, als ich das Gerät drehte. Ich habe 'überschreiben Convenience Init (Ebene: Beliebig) {self.init ()}' hinzugefügt. Jetzt wird kein Fehler angezeigt!
Yuma Technical Inc.
Init (Ebene: Beliebig) hinzugefügt, um den Absturz zu beheben.
Nik Kov
2

Skalierbare Lösung mit CALayer in Swift

Mit dem beschriebenen können InnerShadowLayerSie auch innere Schatten nur für bestimmte Kanten aktivieren, ausgenommen andere. (z. B. können Sie innere Schatten nur am linken und oberen Rand Ihrer Ansicht aktivieren)

Sie können InnerShadowLayerIhrer Ansicht dann ein hinzufügen , indem Sie:

init(...) {

    // ... your initialization code ...

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

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

InnerShadowLayer Implementierung

/// 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
    }
}
Pietro Basso
quelle
1

Dieser Code hat bei mir funktioniert

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()
    }
}
Antony Raphel
quelle
0

Hier gibt es einen Code , der dies für Sie erledigen kann. Wenn Sie die Ebene in Ihrer Ansicht (durch Überschreiben + (Class)layerClass) in JTAInnerShadowLayer ändern, können Sie den inneren Schatten auf der Einrückungsebene in Ihrer Init-Methode festlegen und er erledigt die Arbeit für Sie. Wenn Sie auch den ursprünglichen Inhalt zeichnen möchten, stellen Sie sicher, dass Sie setDrawOriginalImage:yesdie Einrückungsebene aufrufen . Es gibt einen Blog-Beitrag darüber, wie das hier funktioniert .

James Snook
quelle
@MiteshDobareeya Habe gerade beide Links getestet und sie scheinen gut zu funktionieren (auch in einem privaten Tab). Welcher Link hat Ihnen Probleme bereitet?
James Snook
Können Sie sich bitte diese Implementierung des inneren Schattencodes ansehen? Es funktioniert nur in der ViewDidAppear-Methode. Und zeigt etwas Flackern. drive.google.com/open?id=1VtCt7UFYteq4UteT0RoFRjMfFnbibD0E
Mitesh Dobareeya
0

Verwenden der Verlaufsebene:

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];
Johnny Rockex
quelle
0

Es gibt eine einfache Lösung - zeichnen Sie einfach den normalen Schatten und drehen Sie ihn so

@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),
            ])
    }
iago849
quelle