Qual è il modo migliore per aggiungere un'ombra esterna al mio UIView


108

Sto cercando di aggiungere un'ombra discendente alle viste che sono sovrapposte l'una sull'altra, le viste collassano consentendo di vedere il contenuto in altre viste, in questo senso voglio rimanere view.clipsToBoundsATTIVO in modo che quando le viste collassano il loro contenuto venga ritagliato.

Questo sembra aver reso difficile per me aggiungere un'ombra esterna ai livelli poiché quando accendo anche clipsToBoundsle ombre vengono tagliate.

Ho provato a manipolare view.framee view.boundsper aggiungere un'ombra esterna al fotogramma ma consentire ai bordi di essere abbastanza grandi da racchiuderlo, tuttavia non ho avuto fortuna con questo.

Ecco il codice che sto usando per aggiungere un'ombra (funziona solo con clipsToBoundsOFF come mostrato)

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

Ecco uno screenshot dell'ombra applicata allo strato grigio più chiaro in alto. Spero che questo dia un'idea di come il mio contenuto si sovrapporrà se clipsToBoundsè OFF.

Applicazione ombra.

Come posso aggiungere un'ombra al mio UIViewe mantenere il mio contenuto ritagliato?

Modifica: Volevo solo aggiungere che ho anche giocato con l'utilizzo di immagini di sfondo con le ombre, il che funziona bene, tuttavia mi piacerebbe comunque conoscere la migliore soluzione codificata per questo.

Risposte:


280

Prova questo:

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;

Prima di tutto : l' UIBezierPathusato come shadowPathè fondamentale. Se non lo usi, potresti non notare una differenza all'inizio, ma l'occhio attento osserverà un certo ritardo che si verifica durante eventi come la rotazione del dispositivo e / o simili. È un'importante modifica delle prestazioni.

Per quanto riguarda il tuo problema in particolare : la linea importante è view.layer.masksToBounds = NO. Disabilita il ritaglio dei sottolivelli della vista che si estendono oltre i limiti della vista.

Per coloro che si chiedono quale sia la differenza tra masksToBounds(sul livello) e la proprietà della vista clipToBounds: non ce n'è davvero. La commutazione di uno avrà un effetto sull'altro. Solo un diverso livello di astrazione.


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
}

1
Grazie, ho provato il tuo codice e poi ho provato masksToBounds = NO;ad aggiungerlo al mio originale - con entrambi i tentativi ho mantenuto clipsToBounds = YES;ON - entrambi non sono riusciti a ritagliare il contenuto. ecco uno screencap di ciò che è successo con il tuo esempio> youtu.be/tdpemc_Xdps
Wez

1
Qualche idea su come fare in modo che l'ombra esterna abbracci un angolo arrotondato piuttosto che renderlo come se l'angolo fosse ancora un quadrato?
John Erck

7
@JohnErck provare questo: UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. Non testato ma dovrebbe dare il risultato desiderato.
pkluz

1
Ho imparato che durante l'animazione della vista, l'ombra lascia tracce grigie durante il movimento. La rimozione delle linee shadowPath ha risolto questo
problema

1
@shoe perché layoutSubviews()viene chiamata qualsiasi dimensione temporale della vista . E se non compensiamo in quel punto regolando il shadowPathper riflettere la dimensione corrente, avremmo una vista ridimensionata ma l'ombra sarebbe ancora la vecchia dimensione (iniziale) e non mostrerebbe o sbirciare dove non dovrebbe .
pkluz

65

La risposta di Wasabii 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

E 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

Inserisci questo codice in layoutSubviews () se stai usando AutoLayout.

In SwiftUI, tutto questo è molto più semplice:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)

11
Grazie per aver notatolayoutSubviews
Islam Q.

1
o anche meglio in viewDidLayoutSubviews ()
Max MacLeod

1
preferire gli inizializzatori di strutture veloci rispetto alle varianti Make, ovveroCGSize(width: 0, height: 0.5)
Oliver Atkinson

Aggiungo questo codice a layoutSubviews (), non è stato ancora visualizzato in IB. Ci sono altre impostazioni che devo attivare?
asino

Grazie. Stavo usando il layout automatico e ho pensato a me stesso - beh .. view.boundsnon è stato creato in modo così evidente che shadowPathnon può fare riferimento al rettangolo della vista ma ad alcuni CGRectZeroinvece. Comunque, mettere questo sotto ha layoutSubviewsrisolto finalmente il mio problema!
devdc

13

Il trucco è definire masksToBoundscorrettamente la proprietà del livello della vista:

view.layer.masksToBounds = NO;

e dovrebbe funzionare.

( Fonte )


13

È possibile creare un'estensione per UIView per accedere a questi valori nell'editor di progettazione

Opzioni delle ombre nell'editor di progettazione

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

come fare riferimento a tale estensione nel generatore di interfacce
Amr Angry

IBInspectable lo rende disponibile nel generatore di interfacce
Nitesh

Posso ottenere questo risultato anche nell'Obiettivo C?
Harish Pathak

2
se vuoi avere un'anteprima in tempo reale (in Interface Builder), qui puoi trovare un buon articolo a riguardo spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill

7

Puoi anche impostare l'ombra sulla tua vista dallo storyboard

inserisci qui la descrizione dell'immagine


2

In mostra WillLayoutSubviews:

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
}

Utilizzo dell'estensione di 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
    }
}

Uso:

sampleView.addDropShadowToView(sampleView)

1
Basta far rispettare targetView: UIViewe rimuovere la frangia.
bluebamboo

6
Perché non usare self? Mi sembra molto più conveniente.
LinusGeffarth

3
Un modo migliore per farlo è rimuovere targetView come parametro e utilizzare self invece di targetView. Ciò elimina la ridondanza e consente di chiamarla in questo modo: sampleView.addDropShadowToView ()
maxcodes

Il commento di Max Nelson è fantastico. Il mio suggerimento è utilizzare la if letsintassi invece di!
Johnny

0

Quindi sì, dovresti preferire la proprietà shadowPath per le prestazioni, ma anche: Dal file di intestazione di CALayer.shadowPath

Specificare il percorso in modo esplicito utilizzando questa proprietà di solito * migliorerà le prestazioni di rendering, così come la condivisione dello stesso riferimento * del percorso su più livelli

Un trucco meno noto è condividere lo stesso riferimento su più livelli. Ovviamente devono usare la stessa forma, ma questo è comune con le celle della vista tabella / raccolta.

Non so perché diventi più veloce se condividi istanze, immagino che memorizzi nella cache il rendering dell'ombra e possa riutilizzarlo per altre istanze nella vista. Mi chiedo se questo sia ancora più veloce con

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.