Was ist der beste Weg, um meinem UIView einen Schlagschatten hinzuzufügen?

108

Ich versuche, Ansichten, die übereinander liegen, einen Schlagschatten hinzuzufügen. Die Ansichten werden reduziert, sodass Inhalte in anderen Ansichten angezeigt werden können. In diesem Sinne möchte ich eingeschaltet bleiben, view.clipsToBoundsdamit beim Reduzieren der Ansichten deren Inhalte abgeschnitten werden.

Dies scheint es mir schwer gemacht zu haben, den Ebenen einen Schlagschatten hinzuzufügen, da beim clipsToBoundsEinschalten auch die Schatten abgeschnitten werden.

Ich habe versucht zu manipulieren view.frameund view.boundsum dem Rahmen einen Schlagschatten hinzuzufügen, aber die Grenzen sollten groß genug sein, um ihn zu erfassen, aber ich hatte kein Glück damit.

Hier ist der Code, mit dem ich einen Schatten hinzufüge (dies funktioniert nur mit clipsToBoundsAUS wie gezeigt)

view.clipsToBounds = NO;
view.layer.shadowColor = [[UIColor blackColor] CGColor];
view.layer.shadowOffset = CGSizeMake(0,5);
view.layer.shadowOpacity = 0.5;

Hier ist ein Screenshot des Schattens, der auf die hellste graue Ebene angewendet wird. Hoffentlich gibt dies eine Vorstellung davon, wie sich mein Inhalt überschneidet, wenn clipsToBoundsAUS ist.

Schattenanwendung.

Wie kann ich meinem Schatten einen Schatten hinzufügen UIViewund meinen Inhalt beschneiden lassen?

Bearbeiten: Ich wollte nur hinzufügen, dass ich auch mit Hintergrundbildern mit Schatten herumgespielt habe, was gut funktioniert, aber ich würde immer noch gerne die beste codierte Lösung dafür kennen.

Wez
quelle

Antworten:

280

Versuche dies:

UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRect:view.bounds];
view.layer.masksToBounds = NO;
view.layer.shadowColor = [UIColor blackColor].CGColor;
view.layer.shadowOffset = CGSizeMake(0.0f, 5.0f);
view.layer.shadowOpacity = 0.5f;
view.layer.shadowPath = shadowPath.CGPath;

Zuallererst : Das UIBezierPathverwendete als shadowPathist entscheidend. Wenn Sie es nicht verwenden, bemerken Sie möglicherweise zunächst keinen Unterschied, aber das scharfe Auge beobachtet eine gewisse Verzögerung, die bei Ereignissen wie dem Drehen des Geräts und / oder ähnlichem auftritt. Es ist eine wichtige Leistungsoptimierung.

In Bezug auf Ihr Problem speziell : Die wichtige Linie ist view.layer.masksToBounds = NO. Dadurch wird das Abschneiden der Unterebenen der Ansichtsebene deaktiviert, die sich über die Grenzen der Ansicht hinaus erstrecken.

Für diejenigen, die sich fragen, was der Unterschied zwischen masksToBounds(auf der Ebene) und der clipToBoundsEigenschaft der Ansicht ist: Es gibt eigentlich keine. Das Umschalten des einen wirkt sich auf das andere aus. Nur eine andere Abstraktionsebene.


Swift 2.2:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.blackColor().CGColor
    layer.shadowOffset = CGSizeMake(0.0, 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.CGPath
}

Swift 3:

override func layoutSubviews()
{
    super.layoutSubviews()

    let shadowPath = UIBezierPath(rect: bounds)
    layer.masksToBounds = false
    layer.shadowColor = UIColor.black.cgColor
    layer.shadowOffset = CGSize(width: 0.0, height: 5.0)
    layer.shadowOpacity = 0.5
    layer.shadowPath = shadowPath.cgPath
}
pkluz
quelle
1
Vielen Dank, ich habe Ihren Code ausprobiert und dann versucht, ihn masksToBounds = NO;zu meinem Original hinzuzufügen. Bei beiden Versuchen, die ich clipsToBounds = YES;aktiviert habe, konnten beide den Inhalt nicht ausschneiden. Hier ist eine Bildschirmabdeckung dessen, was mit Ihrem Beispiel passiert ist
Wez
1
Irgendwelche Ideen, wie man den Schlagschatten dazu bringt, eine abgerundete Ecke zu umarmen, anstatt zu rendern, als wäre die Ecke noch ein Quadrat?
John Erck
7
@ JohnErck versuchen Sie dies : UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. Nicht getestet, sollte aber das gewünschte Ergebnis liefern.
pkluz
1
Ich habe gelernt, dass der Schatten beim Animieren der Ansicht beim Bewegen graue Spuren hinterlässt. Das Entfernen der shadowPath-Linien löste dieses
Problem
1
@shoe, da sich jede Zeitgröße der Ansicht ändert, layoutSubviews()wird aufgerufen. Und wenn wir dies an dieser Stelle nicht durch Anpassen der shadowPathan die aktuelle Größe anpassen , hätten wir eine vergrößerte Ansicht, aber der Schatten würde immer noch die alte (anfängliche) Größe haben und entweder nicht anzeigen oder herausschauen, wo er nicht sollte .
pkluz
65

Wasabisis Antwort in Swift 2.3:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.blackColor().CGColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.CGPath

Und in Swift 3/4/5:

let shadowPath = UIBezierPath(rect: view.bounds)
view.layer.masksToBounds = false
view.layer.shadowColor = UIColor.black.cgColor
view.layer.shadowOffset = CGSize(width: 0, height: 0.5)
view.layer.shadowOpacity = 0.2
view.layer.shadowPath = shadowPath.cgPath

Fügen Sie diesen Code in layoutSubviews () ein, wenn Sie AutoLayout verwenden.

In SwiftUI ist das alles viel einfacher:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)
Bart van Kuik
quelle
11
Vielen Dank für die NotizlayoutSubviews
Islam Q.
1
oder noch besser in viewDidLayoutSubviews ()
Max MacLeod
1
bevorzugen Sie schnelle Strukturinitialisierer gegenüber Make-Varianten, dhCGSize(width: 0, height: 0.5)
Oliver Atkinson
Ich füge diesen Code zu layoutSubviews () hinzu, er wurde in IB immer noch nicht gerendert. Gibt es andere Einstellungen, die ich aktivieren muss?
Esel
Vielen Dank. Ich habe das automatische Layout verwendet und dachte mir, view.boundsdass shadowPathes nicht so offensichtlich erstellt wurde, dass ich nicht auf das Rechteck der Ansicht verweisen kann, sondern auf einige CGRectZero. Wie auch immer, dies unter zu setzen, layoutSubviewslöste mein Problem endlich!
Devdc
13

Der Trick besteht darin, die masksToBoundsEigenschaft der Ebene Ihrer Ansicht richtig zu definieren:

view.layer.masksToBounds = NO;

und es sollte funktionieren.

( Quelle )

Sergio
quelle
13

Sie können eine Erweiterung für UIView erstellen, um im Entwurfseditor auf diese Werte zuzugreifen

Schattenoptionen im Design-Editor

extension UIView{

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

    @IBInspectable var shadowColor: UIColor{
        get{
            return UIColor(cgColor: self.layer.shadowColor!)
        }
        set{
            self.layer.shadowColor = newValue.cgColor
        }
    }

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

    @IBInspectable var shadowOpacity: Float{
        get{
            return self.layer.shadowOpacity
        }
        set{
            self.layer.shadowOpacity = newValue
        }
    }
}
Chris Stillwell
quelle
Wie verweise ich auf diese Erweiterung im Interface Builder
Amr Angry
IBInspectable stellt es im Interface Builder zur Verfügung
Nitesh
Kann ich dies auch in Ziel C erreichen?
Harish Pathak
2
Wenn Sie eine Echtzeitvorschau (im Interface Builder) haben möchten, finden Sie hier einen guten Artikel darüber spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill
7

Sie können Ihre Ansicht auch über das Storyboard mit Schatten versehen

Geben Sie hier die Bildbeschreibung ein

Pallavi
quelle
2

In viewWillLayoutSubviews:

override func viewWillLayoutSubviews() {
    sampleView.layer.masksToBounds =  false
    sampleView.layer.shadowColor = UIColor.darkGrayColor().CGColor;
    sampleView.layer.shadowOffset = CGSizeMake(2.0, 2.0)
    sampleView.layer.shadowOpacity = 1.0
}

Verwenden der UIView-Erweiterung:

extension UIView {

    func addDropShadowToView(targetView:UIView? ){
        targetView!.layer.masksToBounds =  false
        targetView!.layer.shadowColor = UIColor.darkGrayColor().CGColor;
        targetView!.layer.shadowOffset = CGSizeMake(2.0, 2.0)
        targetView!.layer.shadowOpacity = 1.0
    }
}

Verwendung:

sampleView.addDropShadowToView(sampleView)
AG
quelle
1
Erzwingen targetView: UIViewund entfernen Sie einfach die Pony.
Bluebamboo
6
Warum nicht verwenden self? Scheint mir viel bequemer.
LinusGeffarth
3
Eine bessere Möglichkeit, dies zu tun, besteht darin, targetView als Parameter zu entfernen und self anstelle von targetView zu verwenden. Dies eliminiert Redundanz und ermöglicht es, es wie folgt
aufzurufen
Max Nelsons Kommentar ist großartig. Mein Vorschlag ist, if letSyntax anstelle von!
Johnny zu verwenden
0

Ja, Sie sollten die Eigenschaft shadowPath für die Leistung bevorzugen, aber auch: Aus der Header-Datei von CALayer.shadowPath

Wenn Sie den Pfad explizit mit dieser Eigenschaft angeben, wird * normalerweise die Renderleistung verbessert, und es wird dieselbe Pfadreferenz * über mehrere Ebenen hinweg verwendet

Ein weniger bekannter Trick besteht darin, dieselbe Referenz über mehrere Ebenen hinweg zu teilen. Natürlich müssen sie dieselbe Form verwenden, aber dies ist bei Tabellen- / Sammlungsansichtszellen üblich.

Ich weiß nicht, warum es schneller wird, wenn Sie Instanzen freigeben. Ich vermute, es speichert das Rendering des Schattens zwischen und kann es für andere Instanzen in der Ansicht wiederverwenden. Ich frage mich, ob das mit noch schneller geht

Rufus Mall
quelle