Wie zeichne ich einen Schatten unter einem UIView?

352

Ich versuche, einen Schatten unter den unteren Rand eines UIViewin Cocoa Touch zu zeichnen . Ich verstehe, dass ich CGContextSetShadow()den Schatten zeichnen sollte , aber die 2D-Programmieranleitung für Quarz ist etwas vage:

  1. Speichern Sie den Grafikstatus.
  2. Rufen Sie die Funktion auf CGContextSetShadowund übergeben Sie die entsprechenden Werte.
  3. Führen Sie alle Zeichnungen aus, auf die Sie Schatten anwenden möchten.
  4. Stellen Sie den Grafikstatus wieder her

Ich habe Folgendes in einer UIViewUnterklasse versucht :

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

..aber das funktioniert bei mir nicht und ich bin ein bisschen festgefahren, (a) wohin ich als nächstes gehen soll und (b) ob ich irgendetwas tun muss UIView, damit dies funktioniert?

Fraser Speirs
quelle

Antworten:

98

In Ihrem aktuellen Code speichern Sie GStateden aktuellen Kontext, konfigurieren ihn zum Zeichnen eines Schattens und stellen ihn so wieder her, wie er war, bevor Sie ihn zum Zeichnen eines Schattens konfiguriert haben. Schließlich rufen Sie die Implementierung der Oberklasse auf drawRect:.

Jede Zeichnung, die von der Schatteneinstellung betroffen sein sollte, muss danach erfolgen

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

aber vorher

CGContextRestoreGState(currentContext);

Wenn Sie also möchten, dass die Oberklasse drawRect:in einen Schatten gehüllt wird, wie wäre es dann, wenn Sie Ihren Code so neu anordnen?

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

Ein weitaus einfacherer Ansatz besteht darin, einige Ebenenattribute der Ansicht bei der Initialisierung festzulegen:

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

Sie müssen QuartzCore importieren.

#import <QuartzCore/QuartzCore.h>
ollie
quelle
4
Beachten Sie jedoch, dass dies nur unter iOS 3.2+ funktioniert. Wenn Ihre App auf einer alten Version funktionieren soll, müssen Sie die Lösung von Christian oder ein statisches Bild hinter der Ansicht verwenden, wenn dies eine Option ist.
Florianbuerger
119
Diese Lösung erfordert auch das Hinzufügen #import <QuartzCore/QuartzCore.h>"zur .h-Datei.
MusiGenesis
26
Einstellung masksToBoundsauf NOwird das negieren cornerRadius, nein?
Pixelfreak
4
Okay, um das zu lösen, muss die Hintergrundfarbe auf der Ebene festgelegt und die Ansicht transparent sein.
Pixelfreak
@pixelfreak Wie machst du das? Ich habe es versucht, self.layer.backgroundColor = [[UIColor whiteColor] CGColor];aber kein Glück. Welche Ansicht muss transparent sein?
Victor Van Hee
232
self.layer.masksToBounds = NO;
self.layer.cornerRadius = 8; // if you like rounded corners
self.layer.shadowOffset = CGSizeMake(-15, 20);
self.layer.shadowRadius = 5;
self.layer.shadowOpacity = 0.5;

Dies verlangsamt die Anwendung. Durch Hinzufügen der folgenden Zeile kann die Leistung verbessert werden, solange Ihre Ansicht sichtbar rechteckig ist:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
ZYiOS
quelle
1
Es ist wahrscheinlich erwähnenswert, dass diese Optimierung nur dann nützlich ist, wenn Ihre Ansicht sichtbar rechteckig ist.
Benjamin Dobell
1
self.layer.shadowPath ... statt was? oder einfach nur hinzufügen
Chrizzz
2
Fügen Sie einfach diese zusätzliche Zeile zusätzlich zu den anderen hinzu.
Christophercotton
11
@NathanGaskin - Das Zeichnen von Schatten ist eine teure Operation. Wenn Ihre Anwendung beispielsweise eine andere Ausrichtung der Benutzeroberfläche zulässt und Sie beginnen, das Gerät zu drehen, ohne explizit den Pfad des Schattens anzugeben, muss es während dieser Animation mehrmals gerendert werden. Dies hängt davon ab die Form, wahrscheinlich sichtbar verlangsamen die Animation
Peter Pajchl
9
@BenjaminDobell Diese bestimmte Linie funktioniert nur für Rechtecke, Sie können jedoch auch nicht rechteckige Pfade erstellen. Wenn Sie beispielsweise ein abgerundetes Rechteck haben, können Sie bezierPathWithRoundedRect: CornerRadius:
Dan Dyer
160

Gleiche Lösung, aber nur zur Erinnerung: Sie können den Schatten direkt im Storyboard definieren.

Ex:

Geben Sie hier die Bildbeschreibung ein

Antzi
quelle
2
Leider denke ich, dass CGColor vom Storyboard aus nicht erreichbar ist :(
Antzi
1
Definieren Sie einfach eine Kategorie für UIViewoder CGLayerdie ein UIColorWrapper für die CGColorEigenschaft ist;)
DeFrenZ
1
Dieser Link beschreibt, wie das geht: cocoadventures.org/post/104137872754/…
Kamtschatka
8
Um das Kopieren und Einfügen zu vereinfachen: layer.masksToBound, layer.shadowOffset, layer.shadowRadius, layer.shadowOpacity. Tippfehler bringen dich hier um.
Etayluz
3
layer.shadowOpacity Wert sollte 0,5 nicht 0,5 sein
Desert Rose
43

Sie können dies versuchen ... Sie können mit den Werten spielen. Das bestimmt shadowRadiusdie Menge der Unschärfe. shadowOffsetdiktiert, wohin der Schatten geht.

Swift 2.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height))
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.blackColor().CGColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.CGPath

Swift 3.0

let radius: CGFloat = demoView.frame.width / 2.0 //change it to .height if you need spread for height 
let shadowPath = UIBezierPath(rect: CGRect(x: 0, y: 0, width: 2.1 * radius, height: demoView.frame.height)) 
//Change 2.1 to amount of spread you need and for height replace the code for height

demoView.layer.cornerRadius = 2
demoView.layer.shadowColor = UIColor.black.cgColor
demoView.layer.shadowOffset = CGSize(width: 0.5, height: 0.4)  //Here you control x and y
demoView.layer.shadowOpacity = 0.5
demoView.layer.shadowRadius = 5.0 //Here your control your blur
demoView.layer.masksToBounds =  false
demoView.layer.shadowPath = shadowPath.cgPath

Beispiel mit Spread

Beispiel mit Spread

So erstellen Sie einen einfachen Schatten

    demoView.layer.cornerRadius = 2
    demoView.layer.shadowColor = UIColor.blackColor().CGColor
    demoView.layer.shadowOffset = CGSizeMake(0.5, 4.0); //Here your control your spread
    demoView.layer.shadowOpacity = 0.5 
    demoView.layer.shadowRadius = 5.0 //Here your control your blur

Grundlegendes Schattenbeispiel in Swift 2.0

AUSGABE

O-mkar
quelle
19

Einfache und saubere Lösung mit Interface Builder

Fügen Sie Ihrem Projekt eine Datei mit dem Namen UIView.swift hinzu (oder fügen Sie diese einfach in eine beliebige Datei ein):

import UIKit

@IBDesignable extension UIView {

    /* The color of the shadow. Defaults to opaque black. Colors created
    * from patterns are currently NOT supported. Animatable. */
    @IBInspectable var shadowColor: UIColor? {
        set {
            layer.shadowColor = newValue!.CGColor
        }
        get {
            if let color = layer.shadowColor {
                return UIColor(CGColor:color)
            }
            else {
                return nil
            }
        }
    }

    /* The opacity of the shadow. Defaults to 0. Specifying a value outside the
    * [0,1] range will give undefined results. Animatable. */
    @IBInspectable var shadowOpacity: Float {
        set {
            layer.shadowOpacity = newValue
        }
        get {
            return layer.shadowOpacity
        }
    }

    /* The shadow offset. Defaults to (0, -3). Animatable. */
    @IBInspectable var shadowOffset: CGPoint {
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
    }

    /* The blur radius used to create the shadow. Defaults to 3. Animatable. */
    @IBInspectable var shadowRadius: CGFloat {
        set {
            layer.shadowRadius = newValue
        }
        get {
            return layer.shadowRadius
        }
    }
}

Dies ist dann im Interface Builder für jede Ansicht im Dienstprogrammfenster> Attributinspektor verfügbar:

Dienstprogrammfenster

Sie können den Schatten jetzt einfach einstellen.

Hinweise:
- Der Schatten wird in IB nur zur Laufzeit nicht angezeigt.
- Wie Mazen Kasser sagte

Für diejenigen, die es nicht geschafft haben, [...] zum Laufen zu bringen, stellen Sie sicher, dass Clip Subviews ( clipsToBounds) nicht aktiviert ist

Axel Guilmin
quelle
Dies ist die richtige Antwort - Konfiguration über Code für zukünftige Schnittstellen
Crake
Diese Lösung führt mich zu einem nicht funktionierenden Verhalten mit der folgenden Warnmeldung (eine für jede Eigenschaft):Failed to set (shadowColor) user defined inspected property on (UICollectionViewCell): [<UICollectionViewCell> setValue:forUndefinedKey:]: this class is not key value coding-compliant for the key shadowColor.
Xvolks
1
Ich musste importieren UIKit, damit es funktioniert. Der Foundationvon XCode erstellte Basisimport reicht nicht aus, lässt sich aber gut kompilieren. Ich hätte den gesamten Quellcode kopieren sollen. Vielen Dank an Axel für diese schöne Lösung.
Xvolks
13

Ich benutze dies als Teil meiner Utensilien. Damit können wir nicht nur Schatten setzen, sondern auch für jeden eine abgerundete Ecke bekommen UIView. Sie können auch festlegen, welchen Farbschatten Sie bevorzugen. Normalerweise wird Schwarz bevorzugt, aber manchmal, wenn der Hintergrund nicht weiß ist, möchten Sie vielleicht etwas anderes. Folgendes benutze ich:

in utils.m
+ (void)roundedLayer:(CALayer *)viewLayer 
              radius:(float)r 
              shadow:(BOOL)s
{
    [viewLayer setMasksToBounds:YES];
    [viewLayer setCornerRadius:r];        
    [viewLayer setBorderColor:[RGB(180, 180, 180) CGColor]];
    [viewLayer setBorderWidth:1.0f];
    if(s)
    {
        [viewLayer setShadowColor:[RGB(0, 0, 0) CGColor]];
        [viewLayer setShadowOffset:CGSizeMake(0, 0)];
        [viewLayer setShadowOpacity:1];
        [viewLayer setShadowRadius:2.0];
    }
    return;
}

Um dies zu nutzen, müssen wir dies nennen - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];

Srikar Appalaraju
quelle
7

Swift 3

extension UIView {
    func installShadow() {
        layer.cornerRadius = 2
        layer.masksToBounds = false
        layer.shadowColor = UIColor.black.cgColor
        layer.shadowOffset = CGSize(width: 0, height: 1)
        layer.shadowOpacity = 0.45
        layer.shadowPath = UIBezierPath(rect: bounds).cgPath
        layer.shadowRadius = 1.0
    }
}
neoneye
quelle
Wenn Sie diese Methode wählen würden, würde ich in Betracht ziehen, Parameter hinzuzufügen, damit Sie die Werte so anpassen können, dass sie dynamisch sind
Josh O'Connor
Es gibt mir keine abgerundete Ecke, mache ich etwas falsch?
Nikhil Manapure
@NikhilManapure nichts passiert, dies könnte daran liegen, dass die Funktion installShadow()nicht von viewDidLoad()oderviewWillAppear()
neoneye
Ich habe es aus der überladenen drawSichtmethode aufgerufen. Der Schatten kommt richtig, die abgerundete Ecke jedoch nicht.
Nikhil Manapure
@NikhilManapure keine abgerundeten Ränder, dies könnte daran liegen, dass die Ansicht UIStackViewnur Layout ist und keine Ansicht hat. Möglicherweise müssen Sie UIViewfür alles einen regulären als Container einfügen .
Neoneye
6

Wenn Sie StoryBoard verwenden möchten und keine Laufzeitattribute mehr eingeben möchten, können Sie ganz einfach eine Erweiterung für Ansichten erstellen und diese im Storyboard verwenden.

Schritt 1. Erstellen Sie eine Erweiterung

extension UIView {

@IBInspectable var shadowRadius: CGFloat {
    get {
        return layer.shadowRadius
    }
    set {
        layer.shadowRadius = newValue
    }
}

@IBInspectable var shadowOpacity: Float {
    get {
        return layer.shadowOpacity
    }
    set {
        layer.shadowOpacity = newValue
    }
}

@IBInspectable var shadowOffset: CGSize {
    get {
        return layer.shadowOffset
    }
    set {
        layer.shadowOffset = newValue
    }
}

@IBInspectable var maskToBound: Bool {
    get {
        return layer.masksToBounds
    }
    set {
        layer.masksToBounds = newValue
    }
}
}

Schritt 2. Sie können diese Attribute jetzt im Storyboard verwendenStoryboard-Bild

king_T
quelle
3

Für diejenigen, die dies nicht zum Laufen gebracht haben (wie ich selbst!), Nachdem sie alle Antworten hier ausprobiert haben, stellen Sie einfach sicher, dass die Clip-Unteransichten im Attributinspektor nicht aktiviert sind ...

Mazen Kasser
quelle
1

Swift 3

self.paddingView.layer.masksToBounds = false
self.paddingView.layer.shadowOffset = CGSize(width: -15, height: 10)
self.paddingView.layer.shadowRadius = 5
self.paddingView.layer.shadowOpacity = 0.5
Josh O'Connor
quelle
1

Sie können meine Dienstprogrammfunktion, die für Schatten und Eckenradius erstellt wurde, wie folgt verwenden:

- (void)addShadowWithRadius:(CGFloat)shadowRadius withShadowOpacity:(CGFloat)shadowOpacity withShadowOffset:(CGSize)shadowOffset withShadowColor:(UIColor *)shadowColor withCornerRadius:(CGFloat)cornerRadius withBorderColor:(UIColor *)borderColor withBorderWidth:(CGFloat)borderWidth forView:(UIView *)view{

    // drop shadow
    [view.layer setShadowRadius:shadowRadius];
    [view.layer setShadowOpacity:shadowOpacity];
    [view.layer setShadowOffset:shadowOffset];
    [view.layer setShadowColor:shadowColor.CGColor];

    // border radius
    [view.layer setCornerRadius:cornerRadius];

    // border
    [view.layer setBorderColor:borderColor.CGColor];
    [view.layer setBorderWidth:borderWidth];
}

Hoffe es wird dir helfen !!!

Sandip Patel - SM
quelle
1

Alle Beantworten Sie alle gut, aber ich möchte noch einen Punkt hinzufügen

Wenn Sie bei Tabellenzellen auf ein Problem stoßen, entfernen Sie eine neue Zelle, da der Schatten nicht übereinstimmt. In diesem Fall müssen Sie Ihren Schattencode in eine layoutSubviews-Methode einfügen, damit er sich unter allen Bedingungen gut verhält.

-(void)layoutSubviews{
    [super layoutSubviews];

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

oder platzieren Sie in ViewControllers für eine bestimmte Ansicht Schattencode in der folgenden Methode, damit er gut funktioniert

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

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

Ich habe meine Schattenimplementierung für neue Entwickler für eine allgemeinere Form geändert, z.

/*!
 @brief Add shadow to a view.

 @param layer CALayer of the view.

 */
+(void)applyShadowOnView:(CALayer *)layer OffsetX:(CGFloat)x OffsetY:(CGFloat)y blur:(CGFloat)radius opacity:(CGFloat)alpha RoundingCorners:(CGFloat)cornerRadius{
    UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:layer.bounds cornerRadius:cornerRadius];
    layer.masksToBounds = NO;
    layer.shadowColor = [UIColor blackColor].CGColor;
    layer.shadowOffset = CGSizeMake(x,y);// shadow x and y
    layer.shadowOpacity = alpha;
    layer.shadowRadius = radius;// blur effect
    layer.shadowPath = shadowPath.CGPath;
}
Vishal16
quelle
1

Für andere Xamarians würde die Xamarin.iOS / C # -Version der Antwort wie folgt aussehen:

public override void DrawRect(CGRect area, UIViewPrintFormatter formatter)
{
    CGContext currentContext = UIGraphics.GetCurrentContext();
    currentContext.SaveState();
    currentContext.SetShadow(new CGSize(-15, 20), 5);
    base.DrawRect(area, formatter);
    currentContext.RestoreState();                
}

Der Hauptunterschied besteht darin, dass Sie eine Instanz erwerben, für CGContextdie Sie die entsprechenden Methoden direkt aufrufen.

Martin Zikmund
quelle
1

Sie können dies verwenden Extension, um Schatten hinzuzufügen

extension UIView {

    func addShadow(offset: CGSize, color: UIColor, radius: CGFloat, opacity: Float)
    {
        layer.masksToBounds = false
        layer.shadowOffset = offset
        layer.shadowColor = color.cgColor
        layer.shadowRadius = radius
        layer.shadowOpacity = opacity

        let backgroundCGColor = backgroundColor?.cgColor
        backgroundColor = nil
        layer.backgroundColor =  backgroundCGColor
    }
}

man kann es so nennen

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

Skizzenschatten mit IBDesignable und IBInspectable in Swift 4

WIE MAN ES BENUTZT

DEMO

SKIZZE UND XCODE SEITE FÜR SEITE

Schattenbeispiel

CODE

@IBDesignable class ShadowView: UIView {

    @IBInspectable var shadowColor: UIColor? {
        get {
            if let color = layer.shadowColor {
                return UIColor(cgColor: color)
            }
            return nil
        }
        set {
            if let color = newValue {
                layer.shadowColor = color.cgColor
            } else {
                layer.shadowColor = nil
            }
        }
    }

    @IBInspectable var shadowOpacity: Float {
        get {
            return layer.shadowOpacity
        }
        set {
            layer.shadowOpacity = newValue
        }
    }

    @IBInspectable var shadowOffset: CGPoint {
        get {
            return CGPoint(x: layer.shadowOffset.width, y:layer.shadowOffset.height)
        }
        set {
            layer.shadowOffset = CGSize(width: newValue.x, height: newValue.y)
        }

     }

    @IBInspectable var shadowBlur: CGFloat {
        get {
            return layer.shadowRadius
        }
        set {
            layer.shadowRadius = newValue / 2.0
        }
    }

    @IBInspectable var shadowSpread: CGFloat = 0 {
        didSet {
            if shadowSpread == 0 {
                layer.shadowPath = nil
            } else {
                let dx = -shadowSpread
                let rect = bounds.insetBy(dx: dx, dy: dx)
                layer.shadowPath = UIBezierPath(rect: rect).cgPath
            }
        }
    }
}

AUSGABE

DEMO-AUSGANG

O-mkar
quelle
Sie können den Code verbessern, indem Sie die Schattenpräfixe aus den Attributen entfernen. Das Ziel von ShadowView ist die Schattenoperation. Eine weitere Sache ist, dass Sie dieser Klasse einen Eckenradius hinzufügen können. (
Heutzutage