¿Efecto de sombra interior en la capa UIView?

Tengo el siguiente CALayer:

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

Me gustaría agregarle un efecto de sombra interior , pero no estoy muy seguro de cómo hacerlo. Supongo que se me requerirá dibujar en drawRect, sin embargo, esto agregaría la capa encima de otros objetos UIView, ya que se supone que es una barra detrás de algunos botones, por lo que no sé qué hacer.

Podría agregar otra capa, pero de nuevo, no estoy seguro de cómo lograr el efecto de sombra interno (como este:

enter image description here

Ayuda apreciada …

Para cualquier otra persona que se pregunte cómo dibujar una sombra interna usando Core Graphics según la sugerencia Costique, entonces esta es la forma: (en iOS ajustar según sea necesario)

En tu drawRect: método …

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

Entonces, esencialmente hay los siguientes pasos:

  1. Crea tu camino
  2. Establezca el color de relleno que desea, agregue esta ruta al contexto y complete el contexto
  3. Ahora crea un rectángulo más grande que pueda enlazar la ruta visible. Antes de cerrar esta ruta, agregue la ruta visible. Luego cierre la ruta, de modo que cree una forma con la ruta visible substraída de ella. Es posible que desee investigar los métodos de relleno (devanado distinto de cero de par / impar) dependiendo de cómo haya creado estos caminos. En esencia, para hacer que los subtrazos “resten” cuando los agregas juntos, necesitas dibujarlos (o mejor dicho, construirlos) en direcciones opuestas, uno en el sentido de las agujas del reloj y el otro en el sentido contrario a las agujas del reloj.
  4. Luego debe establecer su ruta visible como el camino de recorte en el contexto, para que no dibuje nada fuera de ella en la pantalla.
  5. Luego configure la sombra en el contexto, que incluye el desplazamiento, el desenfoque y el color.
  6. Luego llena la forma grande con el agujero en ella. El color no importa, porque si has hecho todo bien, no verás este color, solo la sombra.

Sé que llego tarde a esta fiesta, pero esto me habría ayudado a encontrarlo temprano en mis viajes …

Para dar crédito en donde se debe crédito, esto es esencialmente una modificación de la elaboración de Daniel Thorpe sobre la solución de Costique de restar una región más pequeña de una región más grande. Esta versión es para aquellos que usan la composición de capas en lugar de anular -drawRect:

La clase CAShapeLayer se puede usar para lograr el mismo efecto:

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

En este punto, si la capa principal no se enmascara hasta sus límites, verá el área adicional de la capa de máscara alrededor de los bordes de la capa. Esto será de 42 píxeles de negro si acaba de copiar el ejemplo directamente. Para deshacerse de él, simplemente puede usar otro CAShapeLayer con la misma ruta y configurarlo como la máscara de la capa de sombra:

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

No he evaluado esto por mí mismo, pero sospecho que el uso de este enfoque junto con la rasterización es más -drawRect: que la anulación -drawRect:

Es posible dibujar una sombra interna con Core Graphics haciendo una ruta de rectángulo grande fuera de los límites, restando una ruta rectangular de límites y llenando la ruta resultante con una sombra “normal”.

Sin embargo, dado que necesita combinarlo con una capa de degradado, creo que una solución más fácil es crear una imagen PNG transparente de 9 partes de la sombra interna y estirarla al tamaño correcto. La imagen en sombra de 9 partes se vería así (su tamaño es de 21×21 píxeles):

texto alternativo

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

Luego establece el marco de innerShadowLayer y debería estirar la sombra correctamente.

Es un poco redondo, pero evita tener que usar imágenes (léase: fácil de cambiar colores, radio de sombra, etc.) y son solo unas pocas líneas de código.

  1. Agregue un UIImageView como la primera subvista de UIView en la que desea la sombra de gotas. Yo uso IB, pero puedes hacer lo mismo programáticamente.

  2. Suponiendo que la referencia a UIImageView es ‘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]; 

Advertencia: tienes que tener un borde, o la sombra no aparece. [UIColor clearColor] no funciona. En el ejemplo, utilizo un color diferente, pero puedes jugar con él para que tenga el mismo color que el comienzo de la sombra. 🙂

Consulte el comentario de bbrame a continuación sobre la macro UIColorFromRGB .

Una versión simplificada usando solo un CALayer, en 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) } } 

Tenga en cuenta que la capa innerShadow no debe tener un color de fondo opaco, ya que se representará delante de la sombra.

Mejor tarde que nunca…

Aquí hay otro enfoque, probablemente no mejor que los ya publicados, pero es agradable y simple:

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

En lugar de dibujar sombras internas mediante drawRect o agregar UIView a la Vista. Puede agregar CALayer directamente al borde, por ejemplo: si deseo un efecto de sombra interior en la parte inferior 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]; 

Esto agrega una sombra interior inferior para el objective UIView

Aquí hay una versión de swift, cambie startPoint y endPoint para que endPoint en cada lado.

  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) 

Esta es su solución, que he exportado de 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); } 

Llego muy tarde a la fiesta pero me gustaría retribuirle a la comunidad. Este es un método que escribí para eliminar la imagen de fondo de UITextField ya que estaba suministrando una Biblioteca estática y SIN recursos … Utilicé esto para una pantalla de ingreso de PIN de cuatro instancias de UITextField que podría mostrar un carácter en bruto o (BOOL) [self isUsingBullets] o (BOOL) [self usingAsterisks] en ViewController. La aplicación es para retina iPhone / iPhone retina / iPad / iPad, así que no tengo que suministrar cuatro imágenes …

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

Espero que esto ayude a alguien, ya que este foro me ha ayudado.

Consulta el excelente artículo de Inner Shadows in Quartz de Chris Emery que explica cómo PaintCode dibuja las sombras internas y proporciona un fragmento de código limpio y ordenado:

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

Aquí está mi solución en Swift 4.2. ¿Te gustaría probar?

 final class ACInnerShadowLayer : CAShapeLayer { // MARK: Public 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() } } // MARK: Initial 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(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) } } } 

}

Aquí hay un código que puede hacer esto por usted. Si cambia la capa en su vista (anulando + (Class)layerClass ), a JTAInnerShadowLayer entonces puede establecer la sombra interna en la capa de sangrado en su método init y hará el trabajo por usted. Si también desea dibujar el contenido original, asegúrese de llamar a setDrawOriginalImage:yes en la capa de sangrado. Hay una publicación en el blog sobre cómo funciona esto aquí .

Usando Gradient Layer:

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

este código funcionó para mí

 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?.restreGState() } }