Quelle est la meilleure façon d'ajouter une ombre portée à mon UIView

108

J'essaie d'ajouter une ombre portée aux vues superposées, les vues s'effondrent permettant au contenu d'autres vues d'être vu, dans cette veine, je veux rester view.clipsToBoundsactivé afin que lorsque les vues se réduisent, leur contenu soit tronqué.

Cela semble avoir rendu difficile pour moi d'ajouter une ombre portée aux calques, car lorsque clipsToBoundsj'active les ombres sont également coupées.

J'ai essayé de manipuler view.frameet view.boundsd'ajouter une ombre portée au cadre mais de permettre aux limites d'être suffisamment grandes pour l'englober, mais je n'ai pas eu de chance avec cela.

Voici le code que j'utilise pour ajouter une ombre (cela ne fonctionne qu'avec clipsToBoundsOFF comme indiqué)

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

Voici une capture d'écran de l'ombre appliquée au calque gris le plus clair du haut. J'espère que cela donne une idée de la façon dont mon contenu se chevauchera si clipsToBoundsest désactivé.

Application de l'ombre.

Comment puis-je ajouter une ombre à mon UIViewet conserver mon contenu découpé?

Edit: Je voulais juste ajouter que j'ai également joué avec l'utilisation d'images d'arrière-plan avec des ombres, ce qui fonctionne bien, mais j'aimerais toujours connaître la meilleure solution codée pour cela.

Wez
la source

Réponses:

280

Essaye ça:

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;

Tout d'abord : l' UIBezierPathutilisé tel shadowPathquel est crucial. Si vous ne l'utilisez pas, vous ne remarquerez peut-être pas de différence au début, mais l'œil attentif observera un certain décalage se produisant lors d'événements tels que la rotation de l'appareil et / ou similaire. C'est une modification importante des performances.

En ce qui concerne votre problème en particulier : la ligne importante est view.layer.masksToBounds = NO. Elle désactive le découpage des sous-couches de la vue qui s'étendent au-delà des limites de la vue.

Pour ceux qui se demandent quelle est la différence entre masksToBounds(sur le calque) et la clipToBoundspropriété de la vue : il n'y en a pas vraiment. Le basculement de l'un aura un effet sur l'autre. Juste un niveau d'abstraction différent.


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
la source
1
Merci, j'ai essayé votre code, puis masksToBounds = NO;j'ai essayé d'ajouter à mon original - avec les deux tentatives que j'ai gardées clipsToBounds = YES;activées - les deux n'ont pas réussi à couper le contenu. voici une capture d'écran de ce qui s'est passé avec votre exemple> youtu.be/tdpemc_Xdps
Wez
1
Des idées sur la façon de faire en sorte que l'ombre portée épouse un coin arrondi plutôt que de donner l'impression que le coin était toujours un carré?
John Erck
7
@JohnErck essayez ceci: UIBezierPath *shadowPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds cornerRadius:5.0];. Non testé mais devrait donner le résultat souhaité.
pkluz
1
J'ai appris que lors de l'animation de la vue, l'ombre laisse des traces grises en se déplaçant. La suppression des lignes shadowPath a résolu ce problème
ishahak
1
@shoe parce que toute taille de temps de la vue change, layoutSubviews()est appelée. Et si nous ne compensons pas à cet endroit en ajustant le shadowPathpour refléter la taille actuelle, nous aurions une vue redimensionnée mais l'ombre serait toujours l'ancienne taille (initiale) et ne s'afficherait pas ou ne verrait pas où elle ne devrait pas .
pkluz
65

Réponse de Wasabii dans 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

Et dans 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

Mettez ce code dans layoutSubviews () si vous utilisez AutoLayout.

Dans SwiftUI, c'est beaucoup plus simple:

Color.yellow  // or whatever your view
    .shadow(radius: 3)
    .frame(width: 200, height: 100)
Bart van Kuik
la source
11
Merci d'avoir notélayoutSubviews
Islam Q.
1
ou encore mieux dans viewDidLayoutSubviews ()
Max MacLeod
1
préférez les initialiseurs de structure rapide aux variantes de Make, c'estCGSize(width: 0, height: 0.5)
Oliver Atkinson
J'ajoute ce code à layoutSubviews (), il n'a toujours pas été rendu dans IB. Dois-je activer d'autres paramètres?
âne
Merci. J'utilisais la mise en page automatique et je me suis dit - eh bien ... view.boundsn'a pas été créé de manière si évidente que je shadowPathne peux pas faire référence au rect de la vue, mais à certains à la CGRectZeroplace. Quoi qu'il en soit, mettre cela sous layoutSubviewsrésolu mon problème enfin!
devdc
13

L'astuce consiste à définir masksToBoundscorrectement la propriété du calque de votre vue:

view.layer.masksToBounds = NO;

et cela devrait fonctionner.

( Source )

Sergio
la source
13

Vous pouvez créer une extension pour UIView pour accéder à ces valeurs dans l'éditeur de conception

Options d'ombre dans l'éditeur de conception

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
la source
comment faire référence à cette extension dans le constructeur d'interface
Amr Angry
IBInspectable le rend disponible dans le constructeur d'interface
Nitesh
Puis-je également y parvenir en Objectif C?
Harish Pathak
2
si vous voulez avoir un aperçu en temps réel (dans l'interface builder), vous trouverez ici un bon article à ce sujet spin.atomicobject.com/2017/07/18/swift-interface-builder
medskill
7

Vous pouvez également définir une ombre sur votre vue à partir du storyboard

entrez la description de l'image ici

Pallavi
la source
2

Sur la vueWillLayoutSubviews:

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
}

Utilisation de l'extension d'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
    }
}

Usage:

sampleView.addDropShadowToView(sampleView)
AG
la source
1
Il suffit d'appliquer targetView: UIViewet de supprimer la frange.
bluebamboo
6
Pourquoi ne pas l'utiliser self? Cela me semble beaucoup plus pratique.
LinusGeffarth
3
Une meilleure façon de procéder consiste à supprimer targetView en tant que paramètre et à utiliser self au lieu de targetView. Cela élimine la redondance et permet de l'appeler comme suit: sampleView.addDropShadowToView ()
maxcodes
Le commentaire de Max Nelson est excellent. Ma suggestion est d'utiliser la if letsyntaxe au lieu de!
Johnny
0

Donc oui, vous devriez préférer la propriété shadowPath pour les performances, mais aussi: Depuis le fichier d'en-tête de CALayer.shadowPath

Spécifier explicitement le chemin à l'aide de cette propriété améliorera généralement * les performances de rendu, tout comme le partage du même chemin * référence sur plusieurs couches

Une astuce moins connue consiste à partager la même référence sur plusieurs couches. Bien sûr, ils doivent utiliser la même forme, mais c'est courant avec les cellules de vue tableau / collection.

Je ne sais pas pourquoi cela devient plus rapide si vous partagez des instances, je suppose que cela met en cache le rendu de l'ombre et peut le réutiliser pour d'autres instances de la vue. Je me demande si c'est encore plus rapide avec

Centre commercial Rufus
la source