web-dev-qa-db-fra.com

Effet d'ombre interne sur la couche UIView?

J'ai le CALayer suivant:

CAGradientLayer *gradient = [CAGradientLayer layer];
gradient.frame = CGRectMake(8, 57, 296, 30);
gradient.cornerRadius = 3.0f;
gradient.colors = [NSArray arrayWithObjects:(id)[RGB(130, 0, 140) CGColor], (id)[RGB(108, 0, 120) CGColor], nil];
[self.layer insertSublayer:gradient atIndex:0];

Je voudrais ajouter un ombre intérieure effet, mais je ne sais pas trop comment faire cela. Je suppose que je serais obligé de dessiner dans drawRect, mais cela ajouterait le calque au-dessus d'autres objets UIView, car il est censé être une barre derrière certains boutons. Je ne sais donc pas quoi faire?

Je pourrais ajouter un autre calque, mais encore une fois, je ne sais pas comment obtenir l'effet d'ombre interne (comme ceci:

enter image description here

Aide appréciée ...

86
runmad

Pour tous ceux qui se demandent comment dessiner une ombre interne en utilisant Core Graphics selon la suggestion de Costique, voici comment: (sur iOS, ajustez si nécessaire)

Dans votre drawRect: méthode ...

CGRect bounds = [self bounds];
CGContextRef context = UIGraphicsGetCurrentContext();
CGFloat radius = 0.5f * CGRectGetHeight(bounds);


// Create the "visible" path, which will be the shape that gets the inner shadow
// In this case it's just a rounded rect, but could be as complex as your want
CGMutablePathRef visiblePath = CGPathCreateMutable();
CGRect innerRect = CGRectInset(bounds, radius, radius);
CGPathMoveToPoint(visiblePath, NULL, innerRect.Origin.x, bounds.Origin.y);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.Origin.x + innerRect.size.width, bounds.Origin.y);
CGPathAddArcToPoint(visiblePath, NULL, bounds.Origin.x + bounds.size.width, bounds.Origin.y, bounds.Origin.x + bounds.size.width, innerRect.Origin.y, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.Origin.x + bounds.size.width, innerRect.Origin.y + innerRect.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.Origin.x + bounds.size.width, bounds.Origin.y + bounds.size.height, innerRect.Origin.x + innerRect.size.width, bounds.Origin.y + bounds.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, innerRect.Origin.x, bounds.Origin.y + bounds.size.height);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.Origin.x, bounds.Origin.y + bounds.size.height, bounds.Origin.x, innerRect.Origin.y + innerRect.size.height, radius);
CGPathAddLineToPoint(visiblePath, NULL, bounds.Origin.x, innerRect.Origin.y);
CGPathAddArcToPoint(visiblePath, NULL,  bounds.Origin.x, bounds.Origin.y, innerRect.Origin.x, bounds.Origin.y, radius);
CGPathCloseSubpath(visiblePath);

// Fill this path
UIColor *aColor = [UIColor redColor];
[aColor setFill];
CGContextAddPath(context, visiblePath);
CGContextFillPath(context);


// Now create a larger rectangle, which we're going to subtract the visible path from
// and apply a shadow
CGMutablePathRef path = CGPathCreateMutable();
//(when drawing the shadow for a path whichs bounding box is not known pass "CGPathGetPathBoundingBox(visiblePath)" instead of "bounds" in the following line:)
//-42 cuould just be any offset > 0
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the visible path (so that it gets subtracted for the shadow)
CGPathAddPath(path, NULL, visiblePath);
CGPathCloseSubpath(path);

// Add the visible paths as the clipping path to the context
CGContextAddPath(context, visiblePath); 
CGContextClip(context);         


// Now setup the shadow properties on the context
aColor = [UIColor colorWithRed:0.0f green:0.0f blue:0.0f alpha:0.5f];
CGContextSaveGState(context);
CGContextSetShadowWithColor(context, CGSizeMake(0.0f, 1.0f), 3.0f, [aColor CGColor]);   

// Now fill the rectangle, so the shadow gets drawn
[aColor setFill];   
CGContextSaveGState(context);   
CGContextAddPath(context, path);
CGContextEOFillPath(context);

// Release the paths
CGPathRelease(path);    
CGPathRelease(visiblePath);

Donc, essentiellement, il y a les étapes suivantes:

  1. Créez votre chemin
  2. Définissez la couleur de remplissage souhaitée, ajoutez ce chemin au contexte et remplissez le contexte
  3. Créez maintenant un rectangle plus grand pouvant délimiter le chemin visible. Avant de fermer ce chemin, ajoutez le chemin visible. Fermez ensuite le tracé afin de créer une forme en soustrayant le tracé visible. Vous voudrez peut-être examiner les méthodes de remplissage (enroulement différent de zéro de pair/impair) en fonction de la manière dont vous avez créé ces chemins. Essentiellement, pour que les sous-chemins soient "soustraits" lorsque vous les additionnez, vous devez les dessiner (ou plutôt les construire) dans des directions opposées, l'une dans le sens des aiguilles d'une montre et l'autre dans le sens inverse.
  4. Ensuite, vous devez définir votre chemin visible comme tracé de détourage sur le contexte, afin de ne rien dessiner en dehors de celui-ci sur l'écran.
  5. Configurez ensuite l'ombre sur le contexte, ce qui inclut le décalage, le flou et la couleur.
  6. Puis remplissez la grande forme avec le trou dedans. La couleur n'a pas d'importance, car si vous avez tout fait correctement, vous ne verrez pas cette couleur, juste l'ombre.
106
Daniel Thorpe

Je sais que je suis en retard pour cette fête, mais cela m'aurait aidé à trouver tôt dans mes voyages ...

Reconnaître le crédit dû, c’est essentiellement une modification de l’élaboration de Daniel Thorpe sur la solution de Costique consistant à soustraire une région plus petite d’une région plus grande. Cette version est pour ceux qui utilisent la composition de calque au lieu de remplacer -drawRect:

La classe CAShapeLayer peut être utilisée pour obtenir le même effet:

CAShapeLayer* shadowLayer = [CAShapeLayer layer];
[shadowLayer setFrame:[self bounds]];

// Standard shadow stuff
[shadowLayer setShadowColor:[[UIColor colorWithWhite:0 alpha:1] CGColor]];
[shadowLayer setShadowOffset:CGSizeMake(0.0f, 0.0f)];
[shadowLayer setShadowOpacity:1.0f];
[shadowLayer setShadowRadius:5];

// Causes the inner region in this example to NOT be filled.
[shadowLayer setFillRule:kCAFillRuleEvenOdd];

// Create the larger rectangle path.
CGMutablePathRef path = CGPathCreateMutable();
CGPathAddRect(path, NULL, CGRectInset(bounds, -42, -42));

// Add the inner path so it's subtracted from the outer path.
// someInnerPath could be a simple bounds rect, or maybe
// a rounded one for some extra fanciness.
CGPathAddPath(path, NULL, someInnerPath);
CGPathCloseSubpath(path);

[shadowLayer setPath:path];
CGPathRelease(path);

[[self layer] addSublayer:shadowLayer];

À ce stade, si votre calque parent ne masque pas ses limites, vous verrez la zone supplémentaire du calque de masque autour des bords du calque. Ce sera 42 pixels de noir si vous venez de copier l'exemple directement. Pour vous en débarrasser, vous pouvez simplement utiliser une autre CAShapeLayer avec le même chemin et la définir comme masque du calque d'ombre:

CAShapeLayer* maskLayer = [CAShapeLayer layer];
[maskLayer setPath:someInnerPath];
[shadowLayer setMask:maskLayer];

Je n'ai pas mesuré cela moi-même, mais je soupçonne que cette approche associée à la pixellisation est plus performante que le remplacement de -drawRect:.

46
Matt Wilding

Il est possible de dessiner une ombre interne avec Core Graphics en créant un grand tracé rectangle en dehors des limites, en soustrayant un tracé rectangle de la taille d'une limite et en remplissant le tracé obtenu avec une ombre "normale".

Cependant, comme vous devez le combiner avec un calque en dégradé, je pense qu'une solution plus simple consiste à créer une image PNG transparente en 9 parties de l'ombre intérieure et à l'étirer à la bonne taille. L'image de l'ombre en 9 parties ressemblerait à ceci (sa taille est de 21x21 pixels):

alt text

CALayer *innerShadowLayer = [CALayer layer];
innerShadowLayer.contents = (id)[UIImage imageNamed: @"innershadow.png"].CGImage;
innerShadowLayer.contentsCenter = CGRectMake(10.0f/21.0f, 10.0f/21.0f, 1.0f/21.0f, 1.0f/21.0f);

Puis définissez le cadre de innerShadowLayer et il devrait étirer l’ombre correctement.

35
Costique

Une version simplifiée utilisant juste un CALayer, dans Swift:

import UIKit

final class FrameView : UIView {
    init() {
        super.init(frame: CGRect.zero)
        backgroundColor = UIColor.white
    }

    @available(*, unavailable)
    required init?(coder decoder: NSCoder) { fatalError("unavailable") }

    override func layoutSubviews() {
        super.layoutSubviews()
        addInnerShadow()
    }

    private func addInnerShadow() {
        let innerShadow = CALayer()
        innerShadow.frame = bounds
        // Shadow path (1pt ring around bounds)
        let path = UIBezierPath(rect: innerShadow.bounds.insetBy(dx: -1, dy: -1))
        let cutout = UIBezierPath(rect: innerShadow.bounds).reversing()
        path.append(cutout)
        innerShadow.shadowPath = path.cgPath
        innerShadow.masksToBounds = true
        // Shadow properties
        innerShadow.shadowColor = UIColor(white: 0, alpha: 1).cgColor // UIColor(red: 0.71, green: 0.77, blue: 0.81, alpha: 1.0).cgColor
        innerShadow.shadowOffset = CGSize.zero
        innerShadow.shadowOpacity = 1
        innerShadow.shadowRadius = 3
        // Add
        layer.addSublayer(innerShadow)
    }
}

Notez que la couche innerShadow ne doit pas avoir de couleur d'arrière-plan opaque, car celle-ci sera rendue devant l'ombre.

25
Patrick Pijnappel

Un peu comme un aller-retour, mais cela évite d’avoir à utiliser des images (read: changement facile des couleurs, rayon d’ombre, etc.) et ce n’est que quelques lignes de code.

  1. Ajoutez un UIImageView en tant que première sous-vue de UIView sur laquelle vous souhaitez placer l’ombre portée. J'utilise IB, mais vous pouvez faire la même chose par programme.

  2. En supposant que la référence à UIImageView soit "innerShadow"

`

[[innerShadow layer] setMasksToBounds:YES];
[[innerShadow layer] setCornerRadius:12.0f];        
[[innerShadow layer] setBorderColor:[UIColorFromRGB(180, 180, 180) CGColor]];
[[innerShadow layer] setBorderWidth:1.0f];
[[innerShadow layer] setShadowColor:[UIColorFromRGB(0, 0, 0) CGColor]];
[[innerShadow layer] setShadowOffset:CGSizeMake(0, 0)];
[[innerShadow layer] setShadowOpacity:1];
[[innerShadow layer] setShadowRadius:2.0];

Avertissement: vous devez avoir une bordure, sinon l'ombre n'apparaît pas. [UIColor clearColor] ne fonctionne pas. Dans l'exemple, j'utilise une couleur différente, mais vous pouvez la gêner pour qu'elle ait la même couleur que le début de l'ombre. :)

Voir le commentaire de bbrame ci-dessous à propos de la macro UIColorFromRGB.

22
jinglesthula

Mieux vaut tard que jamais...

Voici une autre approche, probablement pas meilleure que celles déjà publiées, mais c'est simple et agréable -

-(void)drawInnerShadowOnView:(UIView *)view
{
    UIImageView *innerShadowView = [[UIImageView alloc] initWithFrame:view.bounds];

    innerShadowView.contentMode = UIViewContentModeScaleToFill;
    innerShadowView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;

    [view addSubview:innerShadowView];

    [innerShadowView.layer setMasksToBounds:YES];

    [innerShadowView.layer setBorderColor:[UIColor lightGrayColor].CGColor];
    [innerShadowView.layer setShadowColor:[UIColor blackColor].CGColor];
    [innerShadowView.layer setBorderWidth:1.0f];

    [innerShadowView.layer setShadowOffset:CGSizeMake(0, 0)];
    [innerShadowView.layer setShadowOpacity:1.0];

    // this is the inner shadow thickness
    [innerShadowView.layer setShadowRadius:1.5];
}
17
SomaMan

Au lieu de dessiner l’ombre intérieure par drawRect ou d’ajouter UIView à la vue. Vous pouvez directement ajouter CALayer à la bordure, par exemple: si je veux un effet d’ombre interne sur le bas de UIView V.

innerShadowOwnerLayer = [[CALayer alloc]init];
innerShadowOwnerLayer.frame = CGRectMake(0, V.frame.size.height+2, V.frame.size.width, 2);
innerShadowOwnerLayer.backgroundColor = [UIColor whiteColor].CGColor;

innerShadowOwnerLayer.shadowColor = [UIColor blackColor].CGColor;
innerShadowOwnerLayer.shadowOffset = CGSizeMake(0, 0);
innerShadowOwnerLayer.shadowRadius = 10.0;
innerShadowOwnerLayer.shadowOpacity = 0.7;

[V.layer addSubLayer:innerShadowOwnerLayer];

Ceci ajoute une ombre interne inférieure pour la cible UIView

8
Jerry Zhang

Voici une version de Swift, changez startPoint et endPoint pour le faire de chaque côté.

        let layer = CAGradientLayer()
        layer.startPoint    = CGPointMake(0.5, 0.0);
        layer.endPoint      = CGPointMake(0.5, 1.0);
        layer.colors        = [UIColor(white: 0.1, alpha: 1.0).CGColor, UIColor(white: 0.1, alpha: 0.5).CGColor, UIColor.clearColor().CGColor]
        layer.locations     = [0.05, 0.2, 1.0 ]
        layer.frame         = CGRectMake(0, 0, self.view.frame.width, 60)
        self.view.layer.insertSublayer(layer, atIndex: 0)
6
William Hu

Voici votre solution que j'ai exportée depuis PaintCode :

-(void) drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();

    //// Shadow Declarations
    UIColor* shadow = UIColor.whiteColor;
    CGSize shadowOffset = CGSizeMake(0, 0);
    CGFloat shadowBlurRadius = 10;

    //// Rectangle Drawing
    UIBezierPath* rectanglePath = [UIBezierPath bezierPathWithRect: self.bounds];
    [[UIColor blackColor] setFill];
    [rectanglePath fill];

    ////// Rectangle Inner Shadow
    CGContextSaveGState(context);
    UIRectClip(rectanglePath.bounds);
    CGContextSetShadowWithColor(context, CGSizeZero, 0, NULL);

    CGContextSetAlpha(context, CGColorGetAlpha([shadow CGColor]));
    CGContextBeginTransparencyLayer(context, NULL);
    {
        UIColor* opaqueShadow = [shadow colorWithAlphaComponent: 1];
        CGContextSetShadowWithColor(context, shadowOffset, shadowBlurRadius, [opaqueShadow CGColor]);
        CGContextSetBlendMode(context, kCGBlendModeSourceOut);
        CGContextBeginTransparencyLayer(context, NULL);

        [opaqueShadow setFill];
        [rectanglePath fill];

        CGContextEndTransparencyLayer(context);
    }
    CGContextEndTransparencyLayer(context);
    CGContextRestoreGState(context);
}
5
Dmitriy Kirakosyan

Je suis très en retard pour la fête mais je voudrais redonner à la communauté .. C’est une méthode que j’ai écrite pour supprimer l’image d’arrière-plan UITextField, car je fournissais une bibliothèque statique et AUCUNE ressource .. Je l'ai utilisé pour un écran de saisie PIN de quatre instances UITextField pouvant afficher Un caractère brut ou (BOOL) [self isUsingBullets] ou (BOOL) [Auto usingAsterisks] dans ViewController . L'application est pour iPhone/iPhone retina/iPad/iPad Retina, je n'ai donc pas à fournir quatre images ...

#import <QuartzCore/QuartzCore.h>

- (void)setTextFieldInnerGradient:(UITextField *)textField
{

    [textField setSecureTextEntry:self.isUsingBullets];
    [textField setBackgroundColor:[UIColor blackColor]];
    [textField setTextColor:[UIColor blackColor]];
    [textField setBorderStyle:UITextBorderStyleNone];
    [textField setClipsToBounds:YES];

    [textField.layer setBorderColor:[[UIColor blackColor] CGColor]];
    [textField.layer setBorderWidth:1.0f];

    // make a gradient off-white background
    CAGradientLayer *gradient = [CAGradientLayer layer];
    CGRect gradRect = CGRectInset([textField bounds], 3, 3);    // Reduce Width and Height and center layer
    gradRect.size.height += 2;  // minimise Bottom shadow, rely on clipping to remove these 2 pts.

    gradient.frame = gradRect;
    struct CGColor *topColor = [UIColor colorWithWhite:0.6f alpha:1.0f].CGColor;
    struct CGColor *bottomColor = [UIColor colorWithWhite:0.9f alpha:1.0f].CGColor;
    // We need to use this fancy __bridge object in order to get the array we want.
    gradient.colors = [NSArray arrayWithObjects:(__bridge id)topColor, (__bridge id)bottomColor, nil];
    [gradient setCornerRadius:4.0f];
    [gradient setShadowOffset:CGSizeMake(0, 0)];
    [gradient setShadowColor:[[UIColor whiteColor] CGColor]];
    [gradient setShadowOpacity:1.0f];
    [gradient setShadowRadius:3.0f];

    // Now we need to Blur the edges of this layer "so it blends"
    // This rasterizes the view down to 4x4 pixel chunks then scales it back up using bilinear filtering...
    // it's EXTREMELY fast and looks ok if you are just wanting to blur a background view under a modal view.
    // To undo it, just set the rasterization scale back to 1.0 or turn off rasterization.
    [gradient setRasterizationScale:0.25];
    [gradient setShouldRasterize:YES];

    [textField.layer insertSublayer:gradient atIndex:0];

    if (self.usingAsterisks) {
        [textField setFont:[UIFont systemFontOfSize:80.0]];
    } else {
        [textField setFont:[UIFont systemFontOfSize:40.0]];
    }
    [textField setTextAlignment:UITextAlignmentCenter];
    [textField setEnabled:NO];
}

J'espère que cela aide quelqu'un comme ce forum m'a aidé.

3
Foxnolds

Consultez le superbe article de Inner Shadows in Quartz de Chris Emery qui explique comment les ombres intérieures sont dessinées par PaintCode et donne un extrait de code net et net:

- (void)drawInnerShadowInContext:(CGContextRef)context
                        withPath:(CGPathRef)path
                     shadowColor:(CGColorRef)shadowColor
                          offset:(CGSize)offset
                      blurRadius:(CGFloat)blurRadius 
{
    CGContextSaveGState(context);

    CGContextAddPath(context, path);
    CGContextClip(context);

    CGColorRef opaqueShadowColor = CGColorCreateCopyWithAlpha(shadowColor, 1.0);

    CGContextSetAlpha(context, CGColorGetAlpha(shadowColor));
    CGContextBeginTransparencyLayer(context, NULL);
        CGContextSetShadowWithColor(context, offset, blurRadius, opaqueShadowColor);
        CGContextSetBlendMode(context, kCGBlendModeSourceOut);
        CGContextSetFillColorWithColor(context, opaqueShadowColor);
        CGContextAddPath(context, path);
        CGContextFillPath(context);
    CGContextEndTransparencyLayer(context);

    CGContextRestoreGState(context);

    CGColorRelease(opaqueShadowColor);
}
3
voiger

Voici ma solution dans Swift 4.2. Voudriez-vous essayer?

final class ACInnerShadowLayer : CAShapeLayer {

  var innerShadowColor: CGColor? = UIColor.black.cgColor {
    didSet { setNeedsDisplay() }
  }

  var innerShadowOffset: CGSize = .zero {
    didSet { setNeedsDisplay() }
  }

  var innerShadowRadius: CGFloat = 8 {
    didSet { setNeedsDisplay() }
  }

  var innerShadowOpacity: Float = 1 {
    didSet { setNeedsDisplay() }
  }

  override init() {
    super.init()

    masksToBounds = true
    contentsScale = UIScreen.main.scale

    setNeedsDisplay()
  }

  required init?(coder aDecoder: NSCoder) {
    fatalError("init(coder:) has not been implemented")
  }

  override func draw(in ctx: CGContext) {
    ctx.setAllowsAntialiasing(true)
    ctx.setShouldAntialias(true)
    ctx.interpolationQuality = .high

    let colorspace = CGColorSpaceCreateDeviceRGB()

    var rect = bounds
    var radius = cornerRadius

    if borderWidth != 0 {
      rect = rect.insetBy(dx: borderWidth, dy: borderWidth)
      radius -= borderWidth
      radius = max(radius, 0)
    }

    let innerShadowPath = UIBezierPath(roundedRect: rect, cornerRadius: radius).cgPath
    ctx.addPath(innerShadowPath)
    ctx.clip()

    let shadowPath = CGMutablePath()
    let shadowRect = rect.insetBy(dx: -rect.size.width, dy: -rect.size.width)
    shadowPath.addRect(shadowRect)
    shadowPath.addPath(innerShadowPath)
    shadowPath.closeSubpath()

    if let innerShadowColor = innerShadowColor, let oldComponents = innerShadowColor.components {
      var newComponets = Array<CGFloat>(repeating: 0, count: 4) // [0, 0, 0, 0] as [CGFloat]
      let numberOfComponents = innerShadowColor.numberOfComponents

      switch numberOfComponents {
      case 2:
        newComponets[0] = oldComponents[0]
        newComponets[1] = oldComponents[0]
        newComponets[2] = oldComponents[0]
        newComponets[3] = oldComponents[1] * CGFloat(innerShadowOpacity)
      case 4:
        newComponets[0] = oldComponents[0]
        newComponets[1] = oldComponents[1]
        newComponets[2] = oldComponents[2]
        newComponets[3] = oldComponents[3] * CGFloat(innerShadowOpacity)
      default:
        break
      }

      if let innerShadowColorWithMultipliedAlpha = CGColor(colorSpace: colorspace, components: newComponets) {
        ctx.setFillColor(innerShadowColorWithMultipliedAlpha)
        ctx.setShadow(offset: innerShadowOffset, blur: innerShadowRadius, color: innerShadowColorWithMultipliedAlpha)
        ctx.addPath(shadowPath)
        ctx.fillPath(using: .evenOdd)
      }
    } 
  }
}
2
Arco

Il y a du code ici qui peut le faire pour vous. Si vous modifiez le calque dans votre vue (en remplaçant + (Class)layerClass) par JTAInnerShadowLayer, vous pouvez définir l'ombre interne du calque d'indent dans votre méthode init et le travail sera fait pour vous. Si vous souhaitez également dessiner le contenu d'origine, veillez à appeler setDrawOriginalImage:yes sur le calque d'indentation. Il y a un article de blog sur la façon dont cela fonctionne ici .

0
James Snook

Utilisation de la couche de dégradé:

UIView * mapCover = [UIView new];
mapCover.frame = map.frame;
[view addSubview:mapCover];

CAGradientLayer * vertical = [CAGradientLayer layer];
vertical.frame = mapCover.bounds;
vertical.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
                        (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                        (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                        (id)[UIColor whiteColor].CGColor, nil];
vertical.locations = @[@0.01,@0.1,@0.9,@0.99];
[mapCover.layer insertSublayer:vertical atIndex:0];

CAGradientLayer * horizontal = [CAGradientLayer layer];
horizontal.frame = mapCover.bounds;
horizontal.colors = [NSArray arrayWithObjects:(id)[UIColor whiteColor].CGColor,
                     (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                     (id)[[UIColor whiteColor] colorWithAlphaComponent:0.0f].CGColor,
                     (id)[UIColor whiteColor].CGColor, nil];
horizontal.locations = @[@0.01,@0.1,@0.9,@0.99];
horizontal.startPoint = CGPointMake(0.0, 0.5);
horizontal.endPoint = CGPointMake(1.0, 0.5);
[mapCover.layer insertSublayer:horizontal atIndex:0];
0
Johnny Rockex

Solution évolutive utilisant CALayer dans Swift

Avec le InnerShadowLayer décrit, vous pouvez également activer les ombres intérieures pour des arêtes spécifiques uniquement, à l'exclusion des autres. (par exemple, vous pouvez activer les ombres intérieures uniquement sur les bords gauche et supérieur de votre vue)

Vous pouvez ensuite ajouter un InnerShadowLayer à votre vue en utilisant:

init(...) {

    // ... your initialization code ...

    super.init(frame: .zero)
    layer.addSublayer(shadowLayer)
}

public override func layoutSubviews() {
    super.layoutSubviews()
    shadowLayer.frame = bounds
}

InnerShadowLayer implémentation

/// Shadow is a struct defining the different kinds of shadows
public struct Shadow {
    let x: CGFloat
    let y: CGFloat
    let blur: CGFloat
    let opacity: CGFloat
    let color: UIColor
}

/// A layer that applies an inner shadow to the specified edges of either its path or its bounds
public class InnerShadowLayer: CALayer {
    private let shadow: Shadow
    private let Edge: UIRectEdge

    public init(shadow: Shadow, Edge: UIRectEdge) {
        self.shadow = shadow
        self.Edge = Edge
        super.init()
        setupShadow()
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    public override func layoutSublayers() {
        updateShadow()
    }

    private func setupShadow() {
        shadowColor = shadow.color.cgColor
        shadowOpacity = Float(shadow.opacity)
        shadowRadius = shadow.blur / 2.0
        masksToBounds = true
    }

    private func updateShadow() {
        shadowOffset = {
            let topWidth: CGFloat = 0
            let leftWidth = Edge.contains(.left) ? shadow.y / 2 : 0
            let bottomWidth: CGFloat = 0
            let rightWidth = Edge.contains(.right) ? -shadow.y / 2 : 0

            let topHeight = Edge.contains(.top) ? shadow.y / 2 : 0
            let leftHeight: CGFloat = 0
            let bottomHeight = Edge.contains(.bottom) ? -shadow.y / 2 : 0
            let rightHeight: CGFloat = 0

            return CGSize(width: [topWidth, leftWidth, bottomWidth, rightWidth].reduce(0, +),
                          height: [topHeight, leftHeight, bottomHeight, rightHeight].reduce(0, +))
        }()

        let insets = UIEdgeInsets(top: Edge.contains(.top) ? -bounds.height : 0,
                                  left: Edge.contains(.left) ? -bounds.width : 0,
                                  bottom: Edge.contains(.bottom) ? -bounds.height : 0,
                                  right: Edge.contains(.right) ? -bounds.width : 0)
        let path = UIBezierPath(rect: bounds.inset(by: insets))
        let cutout = UIBezierPath(rect: bounds).reversing()
        path.append(cutout)
        shadowPath = path.cgPath
    }
}

0
Pietro Basso

ce code a fonctionné pour moi 

class InnerDropShadowView: UIView {
    override func draw(_ rect: CGRect) {
        //Drawing code
        let context = UIGraphicsGetCurrentContext()
        //// Shadow Declarations
        let shadow: UIColor? = UIColor.init(hexString: "a3a3a3", alpha: 1.0) //UIColor.black.withAlphaComponent(0.6) //UIColor.init(hexString: "d7d7da", alpha: 1.0)
        let shadowOffset = CGSize(width: 0, height: 0)
        let shadowBlurRadius: CGFloat = 7.5
        //// Rectangle Drawing
        let rectanglePath = UIBezierPath(rect: bounds)
        UIColor.groupTableViewBackground.setFill()
        rectanglePath.fill()
        ////// Rectangle Inner Shadow
        context?.saveGState()
        UIRectClip(rectanglePath.bounds)
        context?.setShadow(offset: CGSize.zero, blur: 0, color: nil)
        context?.setAlpha((shadow?.cgColor.alpha)!)
        context?.beginTransparencyLayer(auxiliaryInfo: nil)
        do {
            let opaqueShadow: UIColor? = shadow?.withAlphaComponent(1)
            context?.setShadow(offset: shadowOffset, blur: shadowBlurRadius, color: opaqueShadow?.cgColor)
            context!.setBlendMode(.sourceOut)
            context?.beginTransparencyLayer(auxiliaryInfo: nil)
            opaqueShadow?.setFill()
            rectanglePath.fill()
            context!.endTransparencyLayer()
        }
        context!.endTransparencyLayer()
        context?.restoreGState()
    }
}
0
Antony Raphel