Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Inner shadow effect on UIView layer?

I have the following 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]; 

I'd like to add an inner shadow effect to it, but I am not quite sure how to do this. I suppose I would be required to draw in drawRect, however this would add the layer on top of other UIView objects, since it's supposed to be a bar behind some buttons, so I am at a loss as to what to do?

I could add another layer, but again, not sure how to achieve the inner shadow effect (like this:

enter image description here

Help appreciated...

like image 884
runmad Avatar asked Dec 13 '10 16:12

runmad


People also ask

How do I add inner shadow to UIView with rounded corners?

Add subview with the same color which will be centered on the parent and will be with several pixels smaller. Like this you will have space from each side of the parent. On the parent turn on clipping subviews and add shadow to the inner view. Like this, you can have an inner shadow.

What is the function of inner shadow?

The traditional use for an inner shadow is to simulate 3D depth in a 2D image. This is done by creating an offset shadow within a shape to make it look as if it is cut out and casting a shadow on the object beneath it.


2 Answers

For anyone else wondering how to draw an inner shadow using Core Graphics as per Costique's suggestion, then this is how: (on iOS adjust as needed)

In your drawRect: method...

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

So, essentially there are the following steps:

  1. Create your path
  2. Set the fill color you want, add this path to the context, and fill the context
  3. Now create a larger rectangle that can bound the visible path. Before closing this path, add the visible path. Then close the path, so that you create a shape with the visible path subtracted from it. You might want to investigate the fill methods (non-zero winding of even/odd) depending on how you created these paths. In essence, to get the subpaths to "subtract" when you add them together, you need to draw them (or rather construct them) in opposite directions, one clockwise and the other anti-clockwise.
  4. Then you need to set your visible path as the clipping path on the context, so that you don't draw anything outside it to the screen.
  5. Then setup up the shadow on the context, which includes the offset, blur and color.
  6. Then fill the big shape with the hole in it. The color doesn't matter, because if you've done everything right, you won't see this color, just the shadow.
like image 65
Daniel Thorpe Avatar answered Sep 20 '22 15:09

Daniel Thorpe


I know I'm late to this party, but this would have helped me to find early in my travels...

To give credit where credit's due, this is essentially a modification of Daniel Thorpe's elaboration on Costique's solution of subtracting a smaller region from a larger region. This version is for those using layer composition instead of overriding -drawRect:

The CAShapeLayer class can be used to achieve the same effect:

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

At this point, if your parent layer isn't masking to its bounds, you'll see the extra area of the mask layer around the edges of the layer. This will be 42 pixels of black if you just copied the example directly. To get rid of it, you can simply use another CAShapeLayer with the same path and set it as the mask of the shadow layer:

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

I haven't benchmarked this myself, but I suspect that using this approach in conjunction with rasterization is more performant than overriding -drawRect:.

like image 45
Matt Wilding Avatar answered Sep 18 '22 15:09

Matt Wilding