Come posso disegnare un'ombra sotto un UIView?


352

Sto cercando di disegnare un'ombra sotto il bordo inferiore di a UIViewin Cocoa Touch. Capisco che dovrei usare CGContextSetShadow()per disegnare l'ombra, ma la guida alla programmazione 2D Quartz è un po 'vaga:

  1. Salva lo stato grafico.
  2. Chiamare la funzione CGContextSetShadow, passando i valori appropriati.
  3. Esegui tutto il disegno a cui desideri applicare le ombre.
  4. Ripristina lo stato grafico

Ho provato quanto segue in una UIViewsottoclasse:

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

..ma questo non funziona per me e sono un po 'bloccato su (a) dove andare dopo e (b) se c'è qualcosa che devo fare al mio UIViewper farlo funzionare?

Risposte:


98

Nel tuo codice attuale, salvi il GStatecontesto attuale, lo configuri per disegnare un'ombra .. e lo ripristini a quello che era prima di configurarlo per disegnare un'ombra. Quindi, infine, invochi l'implementazione della superclasse di drawRect:.

Qualsiasi disegno che dovrebbe essere influenzato dall'impostazione dell'ombra deve avvenire dopo

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

ma prima

CGContextRestoreGState(currentContext);

Quindi se vuoi che la superclasse drawRect:venga "racchiusa" in un'ombra, che ne dici se riorganizzi il tuo codice in questo modo?

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

789

Un approccio di gran lunga più semplice consiste nell'impostare alcuni attributi di livello della vista all'inizializzazione:

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

Devi importare QuartzCore.

#import <QuartzCore/QuartzCore.h>

4
Ma tieni presente che funziona solo su iOS 3.2+, quindi se la tua app deve funzionare su una versione precedente devi utilizzare la soluzione di Christian o un'immagine statica dietro la vista se questa è un'opzione.
Florianbuerger,

119
Questa soluzione richiede anche l'aggiunta #import <QuartzCore/QuartzCore.h>"al file .h.
MusiGenesis,

26
L'impostazione masksToBoundssu NOannulla il cornerRadius, no?
pixelfreak,

4
Ok, per risolverlo, backgroundColor deve essere impostato sul layer e la vista deve essere trasparente.
pixelfreak,

@pixelfreak Come lo fai? Ci ho provato self.layer.backgroundColor = [[UIColor whiteColor] CGColor];ma senza fortuna. Quale vista deve essere trasparente?
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;

Questo rallenterà l'applicazione. L'aggiunta della seguente riga può migliorare le prestazioni purché la vista sia visibilmente rettangolare:

self.layer.shadowPath = [UIBezierPath bezierPathWithRect:self.bounds].CGPath;

1
Vale probabilmente la pena notare che questa ottimizzazione è utile solo se la vista è visibilmente rettangolare.
Benjamin Dobell,

1
self.layer.shadowPath ... invece di cosa? o semplicemente aggiungendo ad esso
Chrizzz,

2
Basta aggiungere quella linea aggiuntiva oltre alle altre.
christophercotton,

11
@NathanGaskin - disegnare ombre è un'operazione costosa, quindi ad esempio se l'applicazione consente un altro orientamento dell'interfaccia e si inizia a ruotare il dispositivo, senza specificare esplicitamente il percorso dell'ombra che deve essere eseguito il rendering più volte durante l'animazione che, a seconda del la forma, probabilmente rallenta visibilmente l'animazione
Peter Pajchl,

9
@BenjaminDobell Quella particolare linea funziona solo per i rettangoli ma puoi anche creare percorsi non rettangolari. Ad esempio, se hai un rettangolo arrotondato puoi usare bezierPathWithRoundedRect: cornerRadius:
Dan Dyer

160

Stessa soluzione, ma solo per ricordarti: puoi definire l'ombra direttamente nello storyboard.

Ex:

inserisci qui la descrizione dell'immagine


2
Purtroppo, penso che CGColor sia fuori portata dallo storyboard :(
Antzi il

1
basta definire una categoria per UIViewo CGLayerche è un UIColorwrapper per la CGColorproprietà;)
DeFrenZ

1
Questo link descrive come farlo: cocoadventures.org/post/104137872754/…
Kamchatka,

8
Per semplificare la copia e l'incolla delle persone: layer.masksToBound, layer.shadowOffset, layer.shadowRadius, layer.shadowOpacity. Typos ti ucciderà qui.
etayluz,

3
Il valore di layer.shadowOpacity dovrebbe essere 0,5 e non 0,5
Desert Rose,

43

Puoi provare questo .... puoi giocare con i valori. I shadowRadiusdettami la quantità di sfocatura. shadowOffsetdetta dove va l'ombra.

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

Esempio con diffusione

Esempio con diffusione

Per creare un'ombra di base

    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

Esempio Shadow di base in Swift 2.0

PRODUZIONE


19

Soluzione semplice e pulita con Interface Builder

Aggiungi un file chiamato UIView.swift nel tuo progetto (o semplicemente incollalo in qualsiasi file):

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

Quindi questo sarà disponibile in Interface Builder per ogni vista nel pannello Utilità> Impostazioni attributi:

Pannello Utilità

Ora puoi facilmente impostare l'ombra.

Note:
- L'ombra non apparirà in IB, solo in fase di esecuzione.
- Come diceva Mazen Kasser

A coloro che non sono riusciti a farlo funzionare [...] assicurarsi che Clip Subviews ( clipsToBounds) non sia abilitato


Questa è la risposta corretta - configurazione su codice per interfacce future
Crake

Questa soluzione mi porta a un comportamento non funzionante con il seguente messaggio di avviso (uno per ogni proprietà):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
Ho dovuto importare UIKitper farlo funzionare, l' Foundationimportazione di base creata da XCode non è sufficiente, ma si compila bene. Avrei dovuto copiare l'intero codice sorgente. Grazie Axel per questa bella soluzione.
Xvolks

13

Lo uso come parte dei miei programmi di utilità. Con questo non solo possiamo impostare l'ombra, ma possiamo anche ottenere un angolo arrotondato per qualsiasi UIView. Inoltre puoi impostare quale ombra di colore preferisci. Normalmente si preferisce il nero, ma a volte, quando lo sfondo non è bianco, si potrebbe desiderare qualcos'altro. Ecco cosa uso -

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

Per usare questo dobbiamo chiamare questo - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];


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

se seguissi questo metodo prenderei in considerazione l'aggiunta di parametri in modo da poter regolare i valori in modo che sia dinamico
Josh O'Connor,

Non mi sta dando un angolo arrotondato, sto facendo qualcosa di sbagliato?
Nikhil Manapure,

@NikhilManapure non succede nulla, ciò potrebbe essere dovuto al fatto che la funzione installShadow()non viene invocata da viewDidLoad()oviewWillAppear()
neoneye,

Lo stavo chiamando dal drawmetodo di vista sovraccarico . L'ombra sta arrivando correttamente ma l'angolo arrotondato no.
Nikhil Manapure,

@NikhilManapure non ha bordi arrotondati, ciò potrebbe essere dovuto al fatto che la vista è UIStackViewsolo un layout e non ha vista. Potrebbe essere necessario inserire un normale UIViewcome contenitore per tutto.
Neoneye,

6

Se desideri utilizzare StoryBoard e non desideri continuare a digitare gli attributi di runtime, puoi facilmente creare un'estensione per le visualizzazioni e renderle utilizzabili nello storyboard.

Passaggio 1. Creare l'estensione

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

passaggio 2. ora puoi usare questi attributi nello storyboardimmagine dello storyboard


3

A coloro che non sono riusciti a farlo funzionare (come me stesso!) Dopo aver provato tutte le risposte qui, assicurati solo che le Sottopagine Clip non siano abilitate nella finestra di ispezione Attributi ...


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

1

È possibile utilizzare la mia funzione di utilità creata per il raggio di ombra e angolo come di seguito:

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

Spero che ti possa aiutare !!!


1

Tutti Rispondi bene ma voglio aggiungere un altro punto

Se si riscontra un problema quando si hanno celle di tabella, Deque una nuova cella c'è una discrepanza nell'ombra, quindi in questo caso, è necessario posizionare il codice ombra in un metodo layoutSubviews in modo che si comporti bene in tutte le condizioni.

-(void)layoutSubviews{
    [super layoutSubviews];

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

o in ViewController per una vista specifica posizionare il codice ombra all'interno del seguente metodo in modo che funzioni bene

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

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

Ho modificato la mia implementazione shadow per i nuovi sviluppatori per una forma più generalizzata ex:

/*!
 @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;
}

1

Per i compagni Xamariani, la versione Xamarin.iOS / C # della risposta sarebbe simile alla seguente:

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

La differenza principale è che acquisisci un'istanza CGContextsu cui chiami direttamente i metodi appropriati.


1

Puoi usarlo Extensionper aggiungere ombra

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

puoi chiamarlo come

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

0

Sketch Shadow utilizzando IBDesignable e IBInspectable in Swift 4

COME USARLO

DEMO

SKETCH E XCODE LATO PER LATO

Shadow Exampl

CODICE

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

PRODUZIONE

USCITA DEMO


È possibile migliorare il codice rimuovendo i prefissi shadow dagli attributi. L'obiettivo di ShadowView è chiaro sull'operazione ombra. Un'altra cosa è che puoi aggiungere un raggio d'angolo a questa classe. (Oggi, la maggior parte delle viste in ombra coinvolge il raggio dell'angolo)
mathema
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.