Jaki jest najlepszy sposób na dodanie cienia do mojego UIView

108

Próbuję dodać cień do widoków, które są ułożone jeden na drugim, widoki zwijają się, umożliwiając wyświetlanie zawartości w innych widokach, w tym duchu chcę pozostać view.clipsToBoundsWŁĄCZONY, aby po zwinięciu widoków ich zawartość została obcięta.

Wydaje się, że utrudniło mi to dodanie cienia do warstw, ponieważ po włączeniu clipsToBoundscienie również są przycinane.

Próbowałem manipulować view.framei view.boundsdodać cień do ramki, ale pozwoliłem, by granice były wystarczająco duże, aby je objąć, jednak nie miałem z tym szczęścia.

Oto kod, którego używam, aby dodać cień (działa tylko z clipsToBoundsWYŁĄCZONYM, jak pokazano)

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

Oto zrzut ekranu przedstawiający cień nakładany na górną, najjaśniejszą szarą warstwę. Mam nadzieję, że daje to wyobrażenie o tym, jak moje treści będą się nakładać, jeśli clipsToBoundsjest wyłączone.

Aplikacja cienia.

Jak mogę dodać cień do mojej UIViewi zachować obcięte treści?

Edycja: Chciałem tylko dodać, że bawiłem się również przy użyciu obrazów tła z włączonymi cieniami, co działa dobrze, jednak nadal chciałbym poznać najlepiej zakodowane rozwiązanie do tego.

Wez
źródło

Odpowiedzi:

280

Spróbuj tego:

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;

Po pierwsze : UIBezierPathużywany jako shadowPathkluczowy. Jeśli go nie używasz, możesz początkowo nie zauważyć różnicy, ale bystre oko zauważy pewne opóźnienie występujące podczas takich wydarzeń, jak obracanie urządzenia i / lub podobne. To ważna zmiana wydajności.

Odnosząc się konkretnie do twojego problemu : Ważną linią jest view.layer.masksToBounds = NO. Wyłącza przycinanie podwarstw warstwy widoku, które wykraczają poza granice widoku.

Dla tych, którzy zastanawiają się, jaka jest różnica między masksToBounds(na warstwie) a własną clipToBoundswłaściwością widoku : tak naprawdę nie ma. Przełączenie jednego będzie miało wpływ na drugie. Po prostu inny poziom abstrakcji.


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
źródło
1
Dzięki, wypróbowałem twój kod, a następnie spróbowałem dodać masksToBounds = NO;do mojego oryginału - przy obu próbach, które zostawiłem clipsToBounds = YES;WŁĄCZONE - obie nie udało się przyciąć zawartości. oto zrzut ekranu przedstawiający, co się stało z twoim przykładem> youtu.be/tdpemc_Xdps
Wez
1
Jakieś pomysły, jak sprawić, by cień obejmował zaokrąglony róg, zamiast renderować tak, jakby róg był nadal kwadratowy?
John Erck,
7
@JohnErck spróbuj tego: UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. Nie testowane, ale powinno dać oczekiwany rezultat.
pkluz
1
Dowiedziałem się, że podczas animowania widoku cień pozostawia szare ślady podczas ruchu. Usunięcie linii shadowPath rozwiązało ten problem
ishahak
1
@shoe, ponieważ w każdej chwili zmienia się rozmiar widoku layoutSubviews(). A jeśli nie zrekompensujemy tego w tym miejscu, dostosowując shadowPathrozmiar, aby odzwierciedlić bieżący rozmiar, mielibyśmy widok o zmienionym rozmiarze, ale cień nadal miałby stary (początkowy) rozmiar i albo nie byłby wyświetlany, albo wyglądałby tam, gdzie nie powinien .
pkluz
65

Odpowiedź Wasabii w 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

A w 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

Umieść ten kod w module layoutSubviews (), jeśli używasz AutoLayout.

W SwiftUI wszystko jest o wiele łatwiejsze:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)
Bart van Kuik
źródło
11
Dzięki za uwagęlayoutSubviews
Islam Q.
1
lub jeszcze lepiej w widoku DidLayoutSubviews ()
Max MacLeod
1
wolą szybkie inicjatory struktur niż warianty Make, np.CGSize(width: 0, height: 0.5)
Oliver Atkinson
Dodałem ten kod do layoutSubviews (), nadal nie był renderowany w IB. Czy są inne ustawienia, które muszę wybrać?
osioł
Dzięki. Używałem Auto Layout i pomyślałem sobie - no cóż ... view.boundsnie został stworzony tak oczywiście, że shadowPathnie może odnosić się do prostego widoku, ale CGRectZerozamiast tego. W każdym razie, odłożenie tego layoutSubviewsw końcu rozwiązało mój problem!
devdc
13

Sztuczka polega na masksToBoundspoprawnym zdefiniowaniu właściwości warstwy widoku:

view.layer.masksToBounds = NO;

i powinno działać.

( Źródło )

sergio
źródło
13

Możesz utworzyć rozszerzenie dla UIView, aby uzyskać dostęp do tych wartości w edytorze projektu

Opcje cieni w edytorze projektu

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
źródło
jak odnosić się do tego rozszerzenia w kreatorze interfejsów
Amr Angry
IBInspectable udostępnia go w
kreatorze
Czy mogę to osiągnąć również w celu C?
Harish Pathak
2
jeśli chcesz mieć podgląd w czasie rzeczywistym (w kreatorze
medskill
7

Możesz również ustawić cień na swój widok z serii ujęć

wprowadź opis obrazu tutaj

pallavi
źródło
2

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

Korzystanie z rozszerzenia UIView:

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

Stosowanie:

sampleView.addDropShadowToView(sampleView)
AG
źródło
1
Po prostu wymuś targetView: UIViewi usuń grzywkę.
bluebamboo
6
Dlaczego nie używać self? Wydaje mi się o wiele wygodniejsze.
LinusGeffarth
3
Lepszym sposobem na to jest usunięcie targetView jako parametru i użycie self zamiast targetView. To eliminuje nadmiarowość i pozwala nazwać tak: sampleView.addDropShadowToView ()
maxcodes
Komentarz Maxa Nelsona jest świetny. Moja sugestia to użycie if letskładni zamiast!
Johnny
0

Więc tak, powinieneś preferować właściwość shadowPath dla wydajności, ale także: Z pliku nagłówkowego CALayer.shadowPath

Określenie ścieżki jawnie przy użyciu tej właściwości zwykle * poprawia wydajność renderowania, podobnie jak udostępnianie tej samej ścieżki * odniesienia na wielu warstwach

Mniej znaną sztuczką jest udostępnianie tego samego odniesienia na wielu warstwach. Oczywiście muszą używać tego samego kształtu, ale jest to powszechne w przypadku komórek widoku tabeli / kolekcji.

Nie wiem, dlaczego działa szybciej, jeśli udostępniasz instancje, domyślam się, że buforuje renderowanie cienia i może ponownie użyć go w innych instancjach w widoku. Zastanawiam się, czy to jest jeszcze szybsze z

Rufus Mall
źródło