24

I've got a uiview subclass where I'm trying to draw a rounded rectangle with a drop shadow. Though it draws both elements, I can see shadow through the rounded rectangle fill. I'm new to CG so I'm probably missing something simple (though it doesn't seem to be the alpha of the fill which is set to 1). Here's the draw rect code.

- (void)drawRect:(CGRect)rect {
    // get the contect
 CGContextRef context = UIGraphicsGetCurrentContext();

 //for the shadow, save the state then draw the shadow
 CGContextSaveGState(context);
    CGContextSetShadow(context, CGSizeMake(4,-5), 10);



 //now draw the rounded rectangle
 CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
 CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);

 //since I need room in my rect for the shadow, make the rounded rectangle a little smaller than frame
 CGRect rrect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect)-30, CGRectGetHeight(rect)-30);
 CGFloat radius = self.cornerRadius;
 // the rest is pretty much copied from Apples example
 CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
 CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);

 // Start at 1
 CGContextMoveToPoint(context, minx, midy);
 // Add an arc through 2 to 3
 CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
 // Add an arc through 4 to 5
 CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
 // Add an arc through 6 to 7
 CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
 // Add an arc through 8 to 9
 CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
 // Close the path
 CGContextClosePath(context);
 // Fill & stroke the path
 CGContextDrawPath(context, kCGPathFillStroke);

 //for the shadow
  CGContextRestoreGState(context);
}
Martin
  • 1,570
  • 3
  • 19
  • 32
  • 1
    Read this blog post http://bynomial.com/blog/?p=52&cpage=1#comment-1115 . It answers how to do this. – John Friend Sep 27 '11 at 17:09
  • the solution documented here is the best for me, because it shows me how to use a custom image (with maskToBounds =YES) as well as the shadow. thanks! – Mark Nov 12 '11 at 20:04
  • @John: Many many thanks for the link, buddy... Helped me to start. :) – viral Feb 22 '12 at 06:36

3 Answers3

39
- (void)drawRect:(CGRect)rect
{
    self.layer.masksToBounds = NO;
    self.layer.shadowColor = [[UIColor whiteColor] CGColor];
    self.layer.shadowOffset = CGSizeMake(0,2);
    self.layer.shadowRadius = 2;
    self.layer.shadowOpacity = 0.2;

    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:rect byRoundingCorners:UIRectCornerAllCorners cornerRadii:CGSizeMake(20, 20)];
    [[UIColor blackColor] setFill];

    [path fill];
}
Lee Probert
  • 10,308
  • 8
  • 43
  • 70
  • The one caveat here is to make sure that your view's background is set to [UIColor clearColor], otherwise you will get your shadow drawn around the frame of the view, ignoring the rounded corners. – Julian Jul 30 '11 at 18:38
  • 2
    It is unuseful and redundant to set layer properties in drawRect:, since these don't change unless you change them. Move those calls off (e.g. in init...). – millenomi Mar 09 '13 at 23:27
17

I don't think you can do this in a single pass. Hmm I changed your code as follows, which seems to work.

- (void)drawRect:(CGRect)rect
{
    // get the contect
    CGContextRef context = UIGraphicsGetCurrentContext();

    //now draw the rounded rectangle
    CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
    CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 0.0);

    //since I need room in my rect for the shadow, make the rounded rectangle a little smaller than frame
    CGRect rrect = CGRectMake(CGRectGetMinX(rect), CGRectGetMinY(rect), CGRectGetWidth(rect)-30, CGRectGetHeight(rect)-30);
    CGFloat radius = 45;
    // the rest is pretty much copied from Apples example
    CGFloat minx = CGRectGetMinX(rrect), midx = CGRectGetMidX(rrect), maxx = CGRectGetMaxX(rrect);
    CGFloat miny = CGRectGetMinY(rrect), midy = CGRectGetMidY(rrect), maxy = CGRectGetMaxY(rrect);

    {
        //for the shadow, save the state then draw the shadow
        CGContextSaveGState(context);

        // Start at 1
        CGContextMoveToPoint(context, minx, midy);
        // Add an arc through 2 to 3
        CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
        // Add an arc through 4 to 5
        CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
        // Add an arc through 6 to 7
        CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
        // Add an arc through 8 to 9
        CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
        // Close the path
        CGContextClosePath(context);

        CGContextSetShadow(context, CGSizeMake(4,-5), 10);
        CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);

        // Fill & stroke the path
        CGContextDrawPath(context, kCGPathFillStroke);

        //for the shadow
        CGContextRestoreGState(context);
    }

    {
        // Start at 1
        CGContextMoveToPoint(context, minx, midy);
        // Add an arc through 2 to 3
        CGContextAddArcToPoint(context, minx, miny, midx, miny, radius);
        // Add an arc through 4 to 5
        CGContextAddArcToPoint(context, maxx, miny, maxx, midy, radius);
        // Add an arc through 6 to 7
        CGContextAddArcToPoint(context, maxx, maxy, midx, maxy, radius);
        // Add an arc through 8 to 9
        CGContextAddArcToPoint(context, minx, maxy, minx, midy, radius);
        // Close the path
        CGContextClosePath(context);

        CGContextSetStrokeColorWithColor(context, [[UIColor blackColor] CGColor]);
        CGContextSetRGBFillColor(context, 0.0, 0.0, 1.0, 1.0);       

        // Fill & stroke the path
        CGContextDrawPath(context, kCGPathFillStroke);
    }
}
Stefan Arentz
  • 34,311
  • 8
  • 67
  • 88
  • Great, thanks. Makes a lot of sense: you draw the rectangle, draw the shadow, which is above the rounded rectangle, so you finish up by drawing the rectangle again. One note on your code. I had set the background of the view to clear in my init statement. The above code works with any other background, but with a clear background I had to set the fill color before the first rectangle is stroked and filled. – Martin Feb 25 '10 at 18:59
  • I read that you can extend the area of the rendered layer so it isn't clipped by the view frame. Use: layer.bounds =CGRectMake(-1,-1,102,102) ... if your view is 100x100 and the shadow is a pixel offset etc. You get the idea. – Lee Probert Jun 08 '11 at 19:47
15

Try this after you import QuartzCore/QuartzCore.h

yourView.layer.shadowColor = [[UIColor blackColor] CGColor];
yourView.layer.shadowOffset = CGSizeMake(10.0f, 10.0f);
yourView.layer.shadowOpacity = 1.0f;
yourView.layer.shadowRadius = 10.0f;
Jesse Beder
  • 33,081
  • 21
  • 109
  • 146
Romeo Robles
  • 161
  • 1
  • 5