If you donβt dynamically change shape, you probably would be better off just creating an image in the image editor. I know this is easy to create in Photoshop, Illustrator or Fireworks.
However, creating an inner shadow, such as Core Graphics, requires a few steps:
- Lock the shape (using, for example,
CGContextClip or CGContextClipToMask ). - Create a path or mask for everything except the shape.
- Set the shadow options (using
CGContextSetShadowWithColor ). - Fill the path or mask from step 2. This will close the shadow inside the shape, and only the shadow is drawn because you cropped the shape in step 1.
If you do all this correctly, you can get a good result as follows:

Here is the code I wrote to do this. I wrote it in drawRect: subclass of a custom view, but you can easily use this code to draw in any graphic context.
- (void)drawRect:(CGRect)rect { CGContextRef gc = UIGraphicsGetCurrentContext();
First, I create a path that has only an arc:
static CGFloat const kArcThickness = 20.0f; CGRect arcBounds = CGRectInset(self.bounds, 10.0f, 10.0f); CGPoint arcCenter = CGPointMake(CGRectGetMidX(arcBounds), CGRectGetMidY(arcBounds)); CGFloat arcRadius = 0.5f * (MIN(arcBounds.size.width, arcBounds.size.height) - kArcThickness); UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:-M_PI / 3.0 endAngle:-2.0 * M_PI / 3.0 clockwise:NO];
Then I ask Core Graphics to create a new path that is the outline of the arc path. Notice how I ask about this the stroke width kArcThickness and the round line:
CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f);
I also need the return path of this path: a path that includes every point except points in the shape . It so happened (although I donβt think this is documented) that CGContextCreateCopyByStrokingPath and CGPathAddRect draw in opposite directions. Therefore, if I copy a shape and draw a huge rectangle around it, the rule of non-zero wrapping means that the new path will be the opposite of shape :
CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape); CGPathAddRect(shapeInverse, NULL, CGRectInfinite);
Now I can start drawing. First, I will fill the shape with a light gray color:
CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor); CGContextFillPath(gc);
Next, I perform the four steps listed above. I need to save the graphical state so that I can override the clipping and shadow options when done.
CGContextSaveGState(gc); {
Step 1: Fix the shape:
CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextClip(gc);
Step 2: Well, I did this step already when I created shapeInverse .
Step 3: I set the shadow options:
CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor);
Step 4: I fill out the return form from step 2:
CGContextBeginPath(gc); CGContextAddPath(gc, shapeInverse); CGContextFillPath(gc);
Now I restore the state of the graphics, which specifically restores the cropping path and displays the shadow parameters.
} CGContextRestoreGState(gc);
Finally, I will stroke the shape light gray to make the edge sharp:
CGContextSetStrokeColorWithColor(gc, [UIColor colorWithWhite:.75 alpha:1].CGColor); CGContextSetLineWidth(gc, 1); CGContextSetLineJoin(gc, kCGLineCapRound); CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextStrokePath(gc);
Of course I get out when I am done:
CGPathRelease(shape); CGPathRelease(shapeInverse); }
For more complex forms, you can see my answer here and my answer here .
Here is all the code for easy copying:
- (void)drawRect:(CGRect)rect { CGContextRef gc = UIGraphicsGetCurrentContext(); static CGFloat const kArcThickness = 20.0f; CGRect arcBounds = CGRectInset(self.bounds, 10.0f, 10.0f); CGPoint arcCenter = CGPointMake(CGRectGetMidX(arcBounds), CGRectGetMidY(arcBounds)); CGFloat arcRadius = 0.5f * (MIN(arcBounds.size.width, arcBounds.size.height) - kArcThickness); UIBezierPath *arc = [UIBezierPath bezierPathWithArcCenter:arcCenter radius:arcRadius startAngle:-M_PI / 3.0 endAngle:-2.0 * M_PI / 3.0 clockwise:NO]; CGPathRef shape = CGPathCreateCopyByStrokingPath(arc.CGPath, NULL, kArcThickness, kCGLineCapRound, kCGLineJoinRound, 10.0f); CGMutablePathRef shapeInverse = CGPathCreateMutableCopy(shape); CGPathAddRect(shapeInverse, NULL, CGRectInfinite); CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextSetFillColorWithColor(gc, [UIColor colorWithWhite:.9 alpha:1].CGColor); CGContextFillPath(gc); CGContextSaveGState(gc); { CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextClip(gc); CGContextSetShadowWithColor(gc, CGSizeZero, 7, [UIColor colorWithWhite:0 alpha:.25].CGColor); CGContextBeginPath(gc); CGContextAddPath(gc, shapeInverse); CGContextFillPath(gc); } CGContextRestoreGState(gc); CGContextSetStrokeColorWithColor(gc, [UIColor colorWithWhite:.75 alpha:1].CGColor); CGContextSetLineWidth(gc, 1); CGContextSetLineJoin(gc, kCGLineCapRound); CGContextBeginPath(gc); CGContextAddPath(gc, shape); CGContextStrokePath(gc); CGPathRelease(shape); CGPathRelease(shapeInverse); }