Jak narysować cień pod UIView?

352

Próbuję narysować cień pod dolną krawędzią UIVieww Cocoa Touch. Rozumiem, że powinienem CGContextSetShadow()narysować cień, ale przewodnik po programowaniu Quartz 2D jest trochę niejasny:

  1. Zapisz stan grafiki.
  2. Wywołaj funkcję CGContextSetShadow, przekazując odpowiednie wartości.
  3. Wykonaj cały rysunek, do którego chcesz zastosować cienie.
  4. Przywróć stan grafiki

Próbowałem następujące w UIViewpodklasie:

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

.. ale to nie działa dla mnie i trochę utknąłem w (a) gdzie dalej i (b) jeśli jest coś, co muszę zrobić, UIViewaby to zadziałało?

Fraser Speirs
źródło

Odpowiedzi:

98

W bieżącym kodzie zapisujesz GStatebieżący kontekst, konfigurujesz go, aby rysował cień .. i przywracasz go do stanu sprzed skonfigurowania go do rysowania cienia. Następnie w końcu wywołujesz implementację drawRect:

Każdy rysunek, na który powinno mieć wpływ ustawienie cienia, musi nastąpić później

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

ale wcześniej

CGContextRestoreGState(currentContext);

Jeśli więc chcesz, aby nadklasa drawRect:została „owinięta” w cień, to co powiesz na to, jeśli zmienisz kod w ten sposób?

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

Zdecydowanie łatwiejszym podejściem jest ustawienie niektórych atrybutów warstwy widoku podczas inicjalizacji:

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

Musisz zaimportować QuartzCore.

#import <QuartzCore/QuartzCore.h>
ollie
źródło
4
Pamiętaj jednak, że działa to tylko na iOS 3.2+, więc jeśli Twoja aplikacja powinna działać na starej wersji, musisz użyć rozwiązania Christiana lub statycznego obrazu za widokiem, jeśli jest to opcja.
florianbuerger 10.03.11
119
To rozwiązanie wymaga również dodania #import <QuartzCore/QuartzCore.h>"do pliku .h.
MusiGenesis
26
Ustawianie masksToBoundssię NObędą negować cornerRadius, nie?
pixelfreak
4
Ok, aby rozwiązać ten problem, kolor tła musi być ustawiony na warstwie, a widok musi być przezroczysty.
pixelfreak
@pixelfreak Jak to robisz? Próbowałem, self.layer.backgroundColor = [[UIColor whiteColor] CGColor];ale bez powodzenia. Który widok musi być przejrzysty?
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;

Spowolni to działanie aplikacji. Dodanie następującego wiersza może poprawić wydajność, o ile widok jest wyraźnie prostokątny:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;
ZYiOS
źródło
1
Prawdopodobnie warto zauważyć, że ta optymalizacja jest przydatna tylko wtedy, gdy widok jest wyraźnie prostokątny.
Benjamin Dobell,
1
self.layer.shadowPath ... zamiast czego? lub po prostu dodając do tego
Chrizzz,
2
Po prostu dodaj tę dodatkową linię oprócz innych.
christophercotton
11
@NathanGaskin - rysowanie cieni jest kosztowną operacją, więc na przykład jeśli twoja aplikacja pozwala na inną orientację interfejsu i zaczniesz obracać urządzenie, bez wyraźnego określania ścieżki cienia, trzeba go renderować kilka razy podczas animacji, co w zależności od kształt, prawdopodobnie wyraźnie spowalnia animację
Peter Pajchl,
9
@BenjaminDobell Ta konkretna linia działa tylko dla prostokątów, ale możesz także tworzyć ścieżki nieprostokątne. Na przykład, jeśli masz zaokrąglony prostokąt, możesz użyć bezierPathWithRoundedRect: cornerRadius:
Dan Dyer
160

To samo rozwiązanie, ale tylko dla przypomnienia: cień można zdefiniować bezpośrednio w serii ujęć.

Dawny:

wprowadź opis zdjęcia tutaj

Antzi
źródło
2
Niestety myślę, że CGColor jest poza zasięgiem scenorysu :(
Antzi
1
wystarczy zdefiniować kategorię UIViewlub CGLayerktóry jest UIColorwrapper dla CGColormienia;)
DeFrenZ
1
Ten link opisuje, jak to zrobić: cocoadventures.org/post/104137872754/…
Kamczatka,
8
Aby ułatwić ludziom kopiowanie i wklejanie: layer.masksToBound, layer.shadowOffset, layer.shadowRadius, layer.shadowOpacity. Literówki cię tutaj zabiją.
etayluz
3
layer.shadowOpacity wartość powinna wynosić 0,5 a nie 0,5
Desert Rose
43

Możesz spróbować tego ... możesz grać z wartościami. To shadowRadiusdecyduje o stopniu rozmycia. shadowOffsetdecyduje, dokąd idzie cień.

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

Przykład z rozkładem

Przykład z rozkładem

Aby utworzyć podstawowy cień

    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

Podstawowy przykład cienia w Swift 2.0

WYNIK

O-mkar
źródło
19

Proste i czyste rozwiązanie za pomocą Konstruktora interfejsów

Dodaj plik o nazwie UIView.swift do swojego projektu (lub po prostu wklej go w dowolnym pliku):

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

Będzie to dostępne w Konstruktorze interfejsów dla każdego widoku w panelu Narzędzia> Inspektor atrybutów:

Panel narzędzi

Teraz możesz łatwo ustawić cień.

Uwagi:
- Cień nie pojawi się w IB, tylko w czasie wykonywania.
- Jak powiedział Mazen Kasser

Dla tych, którym nie udało się clipsToBoundsuruchomić tego [...] upewnij się, że Clip Subviews ( ) nie jest włączony

Axel Guilmin
źródło
To jest poprawna odpowiedź - konfiguracja za pomocą kodu dla przyszłych interfejsów
Crake
To rozwiązanie prowadzi mnie do niedziałającego zachowania z następującym komunikatem ostrzegawczym (po jednym dla każdej właściwości):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
Musiałem zaimportować, UIKitaby działało, podstawowy Foundationimport utworzony przez XCode nie wystarczy, ale dobrze się kompiluje. Powinienem był skopiować cały kod źródłowy. Dziękuję Axel za to miłe rozwiązanie.
Xvolks,
13

Używam tego jako części moich narzędzi. Dzięki temu możemy nie tylko ustawić cień, ale także uzyskać zaokrąglony narożnik dla dowolnego UIView. Możesz także ustawić preferowany cień. Zwykle czarny jest preferowany, ale czasami, gdy tło nie jest białe, możesz chcieć czegoś innego. Oto, czego używam -

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

Aby tego użyć, musimy nazwać to - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];

Srikar Appalaraju
źródło
7

Szybki 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
źródło
jeśli wybrałeś tę metodę, rozważyłbym dodanie parametrów, abyś mógł dostosować wartości tak, aby były dynamiczne
Josh O'Connor
Nie daje mi zaokrąglonego rogu, czy robię coś złego?
Nikhil Manapure
@NikhilManapure nic się nie dzieje, może to być spowodowane tym, że funkcja installShadow()nie jest wywoływana z viewDidLoad()lubviewWillAppear()
neoneye,
Dzwoniłem do niego z przeciążonej drawmetody widzenia. Cień nadchodzi poprawnie, ale zaokrąglony róg nie.
Nikhil Manapure
@NikhilManapure nie ma zaokrąglonych ramek, może to być spowodowane tym, że widok jest UIStackViewukładem i nie ma widoku. Być może będziesz musiał wstawić zwykły UIViewjako pojemnik na wszystko.
neoneye
6

Jeśli chcesz korzystać z StoryBoard i nie chcesz nadal wpisywać atrybutów środowiska wykonawczego, możesz łatwo utworzyć rozszerzenie widoków i umożliwić ich użycie w scenorysie.

Krok 1. Utwórz rozszerzenie

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

krok 2. teraz możesz używać tych atrybutów w serii ujęćobraz serii ujęć

king_T
źródło
3

Do tych, którym nie udało się uzyskać tego (jak ja!) Po wypróbowaniu wszystkich odpowiedzi tutaj, upewnij się, że Inspekcja atrybutów nie jest włączona w Inspektorze atrybutów ...

Mazen Kasser
źródło
1

Szybki 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
źródło
1

Możesz użyć mojej funkcji narzędzia utworzonej dla cienia i promienia narożnika, jak poniżej:

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

Mam nadzieję, że ci to pomoże !!!

Sandip Patel - SM
źródło
1

Wszystko Odpowiedz dobrze, ale chcę dodać jeszcze jeden punkt

Jeśli napotkasz problem, gdy masz komórki tabeli, Deque nowej komórki ma niezgodność w cieniu, więc w tym przypadku musisz umieścić kod cienia w metodzie layoutSubviews, aby działał ładnie we wszystkich warunkach.

-(void)layoutSubviews{
    [super layoutSubviews];

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

lub w ViewControllers dla określonego widoku umieść kod cienia wewnątrz następującej metody, aby działał dobrze

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

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

Zmodyfikowałem moją implementację cienia dla nowych programistów, aby uzyskać bardziej uogólnioną formę, np .:

/*!
 @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
źródło
1

Dla innych Xamarian wersja Xamarin.iOS / C # wygląda następująco:

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

Główną różnicą jest to, że nabywasz instancję, CGContextw której bezpośrednio wywołujesz odpowiednie metody.

Martin Zikmund
źródło
1

Możesz użyć tego, Extensionaby dodać cień

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

możesz to tak nazwać

your_Custom_View.addShadow(offset: CGSize(width: 0, height: 1), color: UIColor.black, radius: 2.0, opacity: 1.0)
pigeon_39
źródło
0

Szkicuj cień za pomocą IBDesignable i IBInspectable w Swift 4

JAK TEGO UŻYĆ

PRÓBNY

SZKIC I XCODE STRONIE PO STRONIE

Shadow Exampl

KOD

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

WYNIK

WYJŚCIE DEMO

O-mkar
źródło
Możesz poprawić kod usuwając prefiksy cienia z atrybutów. Celem ShadowView jest jasne działanie cienia. Jeszcze jedna rzecz: możesz dodać promień narożnika do tej klasy. (W dzisiejszych czasach większość widoków cienia wymaga promienia narożnika)
matemat