web-dev-qa-db-fra.com

Comment dessiner une ombre sous UIView?

J'essaie de dessiner une ombre sous le bord inférieur d'une UIView dans Cocoa Touch. Je comprends que je devrais utiliser CGContextSetShadow() pour dessiner l’ombre, mais le guide de programmation de Quartz 2D est un peu vague:

  1. Enregistrez l'état graphique.
  2. Appelez la fonction CGContextSetShadow en transmettant les valeurs appropriées.
  3. Effectuez tout le dessin auquel vous souhaitez appliquer des ombres.
  4. Restaurer l'état graphique

J'ai essayé ce qui suit dans une sous-classe UIView:

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

..mais cela ne fonctionne pas pour moi et je suis un peu coincé sur (a) où aller ensuite et (b) si je dois faire quelque chose à ma UIView pour que cela fonctionne?

339
Fraser Speirs

Dans votre code actuel, vous enregistrez la GState du contexte actuel, le configurez pour dessiner une ombre .. et restaurez ce qu'il était avant de le configurer pour dessiner une ombre. Enfin, vous appelez enfin l'implémentation de drawRect:. Dans la super-classe.

Tout dessin qui devrait être affecté par le paramètre d'ombre doit se produire après

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

mais avant

CGContextRestoreGState(currentContext);

Donc, si vous voulez que le drawRect: de la super-classe soit "enveloppé" dans une ombre, alors que diriez-vous si vous réorganisez votre code comme ceci?

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

Une approche beaucoup plus simple consiste à définir certains attributs de couche de la vue lors de l’initialisation:

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

Vous devez importer QuartzCore.

#import <QuartzCore/QuartzCore.h>
781
ollie
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;

Cela ralentira l'application. L'ajout de la ligne suivante peut améliorer les performances tant que votre vue est visiblement rectangulaire:

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

Même solution, mais juste pour vous rappeler: vous pouvez définir l'ombre directement dans le storyboard.

Ex:

enter image description here

154
Antzi

Vous pouvez essayer ceci .... vous pouvez jouer avec les valeurs . Le shadowRadius détermine la quantité de flou. shadowOffset dicte où l'ombre va.

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

Exemple avec propagation

 Example with spread

Pour créer une ombre de 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

Exemple d'ombre de base dans Swift 2.0

 OUTPUT

40
O-mkar

Solution simple et propre à l'aide d'Interface Builder

Ajoutez un fichier nommé UIView.Swift dans votre projet (ou collez-le simplement dans n’importe quel fichier):

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

Cela sera ensuite disponible dans Interface Builder pour chaque vue du panneau Utilitaires> Inspecteur des attributs:

 Utilities Panel

Vous pouvez facilement définir l'ombre maintenant.

Remarques:
- L'ombre n'apparaîtra pas dans IB, uniquement au moment de l'exécution.
- Comme l'a dit Mazen Kasser

Pour ceux qui n'ont pas réussi à faire fonctionner [...] cela, assurez-vous que les sous-vues de clip (clipsToBounds) ne sont pas activées

18
Axel Guilmin

J'utilise cela dans le cadre de mes utils. Avec cela, nous pouvons non seulement définir une ombre, mais également obtenir un angle arrondi pour toute variable UIView. Aussi, vous pouvez définir quelle couleur d'ombre vous préférez. Normalement, le noir est préférable, mais parfois, lorsque l'arrière-plan n'est pas blanc, vous souhaiterez peut-être autre chose. Voici ce que j'utilise -

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

Pour utiliser cela, nous devons appeler ceci - [utils roundedLayer:yourview.layer radius:5.0f shadow:YES];

13
Srikar Appalaraju

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

Pour ceux qui n'ont pas réussi à faire fonctionner cela (comme moi!) Après avoir essayé toutes les réponses ici, assurez-vous simplement que Clip Subviews n'est pas activé sur l'inspecteur des attributs.

3
Mazen Kasser

Si vous souhaitez utiliser StoryBoard sans vouloir continuer à taper des attributs d'exécution, vous pouvez facilement créer une extension aux vues et les rendre utilisables dans le storyboard.

Étape 1. créer une extension

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

étape 2. vous pouvez maintenant utiliser ces attributs dans le storyboard  storyboard image

2
king_T

Tous réponds bien mais je veux ajouter un point 

Si vous rencontrez un problème lorsque vous avez des cellules de tableau, que vous omettez une nouvelle cellule, il est nécessaire de placer votre code fantôme dans une méthode layoutSubviews afin qu'il se comporte bien dans toutes les conditions.

-(void)layoutSubviews{
    [super layoutSubviews];

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

ou dans ViewControllers pour une vue spécifique, placez du code fantôme dans la méthode suivante afin que tout fonctionne correctement

-(void)viewDidLayoutSubviews{
    [super viewDidLayoutSubviews];

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

J'ai modifié mon implémentation shadow pour les nouveaux développeurs pour une forme plus généralisée 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
Vishal16

Vous pouvez utiliser la fonction utilitaire créée pour l'ombre et le rayon de l'angle, comme ci-dessous:

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

J'espère que ça vous aidera !!!

1
Sandip Patel - SM

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
0
Josh O'Connor

Pour les collègues Xamarians, la version de la réponse sur Xamarin.iOS/C # ressemblerait à ceci:

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 principale différence est que vous acquérez une instance de CGContext sur laquelle vous appelez directement les méthodes appropriées.

0
Martin Zikmund