Core Graphics Tutorial: Shadows and Gloss

Ray Wenderlich
Custom draw the header of this table!

Custom draw the header of this table!

Update 4/16/2013 Fully updated for Xcode 4.6, and ARC. (original post by Ray Wenderlich, update by Brian Moakley).

This is the second part of a tutorial series covering how to get started with the Core Graphics API – via practical examples!

The first part of the series covered how to draw lines, rectangles, gradients – via making pretty table view cell backgrounds.

In this article, you’ll move on to customizing the header for the table. Along the way, you’ll solidify some of your existing knowledge, and learn how to draw shadows and gloss effects!

If you don’t have it already, grab a copy of the sample project from that last Core Graphics tutorial.

Getting Started

The next step in the table view customization is to beautify the table view header. To get started, you’ll color the entire table view header red with Core Graphics, just to make sure that it works.

Go to File\New\File…, choose the iOS\Cocoa Touch Class\Objective-C class template, and click Next. In the next menu, enter the name CustomHeader as the class name. In the subclass field, type UIView. Click Next then Create.

Switch over to CustomHeader.m, uncomment drawRect, and replace the contents with the following:

-(void) drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();
    UIColor * redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
 
    CGContextSetFillColorWithColor(context, redColor.CGColor);
    CGContextFillRect(context, self.bounds);
}

You should be familiar with what this does already, since you did the same exact thing in the previous Core Graphics tutorial.

Now integrate this into the table header view. To do so, switch to CoolTableViewController.m and make the following modifications:

// In import section
#import "CustomHeader.h"
 
// Add new methods
-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    return [[CustomHeader alloc] init];   
}
 
-(CGFloat) tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section 
{
    return 50;
}

To replace the view for the header in a table view, all you have to do is implement viewForHeaderInSection. You also need to implement heightForHeaderInSection so the table view knows how tall to make the view.

Build and run the project, and if all goes well you should see the following:

Placeholders for Custom Header

Ok great – now you’ll fill in that area with a good looking view!

Your Goal

Time for a memory refresh. Here’s a zoomed in view of the header you want to make:

Zoomed in view of Header

Note the following about the above:

  • The blue area is a gradient from light blue to dark blue.
  • The top of the blue area has a gloss effect.
  • There’s a dark blue border around the edges of the blue area.
  • There’s a slight shadow at the bottom of the box.
  • The top of the paper area is drawn, to lead into the top cell.
  • The table view controller will need to set the color and label for each view.

A lot of this will be a good refresher from last Core Graphics tutorial and the custom UIView tutorial, but you’ll cover some new stuff along the way as well!

Getting Set Up

The first task at hand is to do some basic setup. You’re going to need to allow the table view to set the color of the header and some text to display, and set up some rectangles where you’re going to draw the various pieces (such as the header bar and the paper dropdown).

So let’s give that a shot. Replace CustomHeader.h with the following:

#import <UIKit/UIKit.h>
 
@interface CustomHeader : UIView
 
@property (nonatomic, strong) UILabel * titleLabel;
@property (nonatomic, strong) UIColor * lightColor;
@property (nonatomic, strong) UIColor * darkColor;
 
@end

Here you just declared three properties – one for the label to display in the header, and two to store the colors for the light and dark part of the gradient in the header (which will be blue for you).

Then replace CustomHeader.m with the following:

#import "CustomHeader.h"
#import "Common.h"
 
@interface CustomHeader()
 
@property (nonatomic, assign) CGRect coloredBoxRect;
@property (nonatomic, assign) CGRect paperRect;
 
@end
 
@implementation CustomHeader
 
- (id)init 
{
    self = [super init];
    if (self) {
        self.backgroundColor = [UIColor clearColor];
        self.opaque = NO;
        _titleLabel = [[UILabel alloc] init];
        _titleLabel.textAlignment = NSTextAlignmentCenter;
        _titleLabel.opaque = NO;
        _titleLabel.backgroundColor = [UIColor clearColor];
        _titleLabel.font = [UIFont boldSystemFontOfSize:20.0];
        _titleLabel.textColor = [UIColor whiteColor];
        _titleLabel.shadowColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
        _titleLabel.shadowOffset = CGSizeMake(0, -1);
        [self addSubview:_titleLabel];
        _lightColor = [UIColor colorWithRed:105.0f/255.0f green:179.0f/255.0f blue:216.0f/255.0f alpha:1.0];
        _darkColor = [UIColor colorWithRed:21.0/255.0 green:92.0/255.0 blue:136.0/255.0 alpha:1.0];        
    }
    return self;
}
 
-(void) layoutSubviews 
{       
    CGFloat coloredBoxMargin = 6.0;
    CGFloat coloredBoxHeight = 40.0;
    self.coloredBoxRect = CGRectMake(coloredBoxMargin, coloredBoxMargin, self.bounds.size.width-coloredBoxMargin*2, coloredBoxHeight);
 
    CGFloat paperMargin = 9.0;
    self.paperRect = CGRectMake(paperMargin, CGRectGetMaxY(self.coloredBoxRect), self.bounds.size.width-paperMargin*2, self.bounds.size.height-CGRectGetMaxY(self.coloredBoxRect));
 
    self.titleLabel.frame = self.coloredBoxRect;       
}
 
// Replace drawRect with the following
- (void)drawRect:(CGRect)rect 
{    
    CGContextRef context = UIGraphicsGetCurrentContext();
 
    UIColor * redColor = [UIColor colorWithRed:1.0 green:0.0 blue:0.0 alpha:1.0];
    UIColor * greenColor = [UIColor colorWithRed:0.0 green:1.0 blue:0.0 alpha:1.0];
 
    CGContextSetFillColorWithColor(context, redColor.CGColor);
    CGContextFillRect(context, self.coloredBoxRect);
 
    CGContextSetFillColorWithColor(context, greenColor.CGColor);
    CGContextFillRect(context, self.paperRect);
}
 
@end

Most of this should look familiar to you based on prior tutorials on this site.

In the init method, you set up a label with some default values. Note you set a slight shadow to the top of the text, to make it look indented. Reasoning: if the light is to the top, and something is indented, there will be a shadow on the upper inside.

You also make sure the view is non-opaque (i.e. transparent) since you want the background to show through a little bit here.

layoutSubviews is called when you view changes size. Here is where you add code to calculate the rectangle for where you’ll draw the colored box, and where you’ll draw the paper. Each is a straightforward calculation.

Finally, you just color each of the boxes in drawRect with a different color to make sure you’ve got the calculations right.

One last step – inside CoolTableViewController.m, replace tableView:viewForHeaderInSection with the following:

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    CustomHeader * header = [[CustomHeader alloc] init];
    header.titleLabel.text = [self tableView: tableView titleForHeaderInSection:section];
    return header;
}

This sets the title label in the header to the appropriate value.

Build and run the app, and you should see the following:

Header colored with subrects to draw

Getting much closer! As you can see, coloring rects is a handy debugging tool.

Drawing Drop Shadows

For drawing, you should start with the bottom up, so you’ll draw the paper, then the shadow, then the colored box. That is because drawing that occurs later will be drawn on top of the rest.

Drawing the paper is easy – you’re just going to fill that rectangle with white.

But how do you draw the shadows? Well, in Core Graphics, to draw a shadow, you simply call a function to enable drawing shadows, and then draw a path. A shadow will then be drawn under the path with the parameters you specified – regardless of the shape of the path!

In CustomHeader.m, replace the contents of drawRect with the following:

-(void) drawRect:(CGRect)rect
{
    CGContextRef context = UIGraphicsGetCurrentContext();    
 
    UIColor * whiteColor = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:1.0];
    UIColor * shadowColor = [UIColor colorWithRed:0.2 green:0.2 blue:0.2 alpha:0.5];   
 
    CGContextSetFillColorWithColor(context, whiteColor.CGColor);
    CGContextFillRect(context, _paperRect);
 
    CGContextSaveGState(context);
    CGContextSetShadowWithColor(context, CGSizeMake(0, 2), 3.0, shadowColor.CGColor);
    CGContextSetFillColorWithColor(context, self.lightColor.CGColor);
    CGContextFillRect(context, self.coloredBoxRect);
    CGContextRestoreGState(context);
}

Skipping over the colors and paper drawing (you should understand that by now), you call the function CGContextSetShadowWithColor to set up shadow drawing.

The first parameter is the offset of the shadow to draw. Here you set it up to draw 2 points underneath the given path.

The next parameter is the blur of the shadow. A value of 0 would be a hard edge, the larger the values are the softer the edge. You set the value to 3 for a soft edge.

Finally you specify the shadow color. Note you use a gray color, with an alpha of 0.5 (halfway transparent), which lends to a more realistic shadow.

After setting up the shadows, you fill the colored box rectangle with the light color. The act of filling that path will cause the shadow to be applied.

One last change – now that you’re actually using the given color, open CoolTableViewController.m and modify tableView:viewForHeaderInSection: to change the color for section 1:

-(UIView *) tableView:(UITableView *)tableView viewForHeaderInSection:(NSInteger)section
{
    CustomHeader * header = [[CustomHeader alloc] init];
    header.titleLabel.text = [self tableView: tableView titleForHeaderInSection:section];
    // START NEW
    if (section == 1) {
        header.lightColor = [UIColor colorWithRed:147.0/255.0 green:105.0/255.0 blue:216.0/255.0 alpha:1.0];
        header.darkColor = [UIColor colorWithRed:72.0/255.0 green:22.0/255.0 blue:137.0/255.0 alpha:1.0];
    }
    // END NEW
    return header;
}

Build and run the project, and you should now see the following:

Headers with Shadow

Wow – it’s looking pretty good already! But let’s add a little extra polish with a gradient, gloss, and border up top.

Adding a Gloss Effect

To add a gloss effect to a button in Core Graphics, things can get pretty complicated. If you’re feeling hardcore, check out some great work by Matt Gallagher and Michael Heyeck on the matter.

But to my poor eyes, you can get a pretty good looking approximation of a gloss effect just by applying a gradient alpha mask, which is much simpler to understand and code, so you’re going to go with that.

This is something you’ll use later on as well, so add the following to Common.h:

void drawGlossAndGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor);

And the following to Common.m:

void drawGlossAndGradient(CGContextRef context, CGRect rect, CGColorRef startColor, CGColorRef endColor)
{
    drawLinearGradient(context, rect, startColor, endColor);
 
    UIColor * glossColor1 = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.35];
    UIColor * glossColor2 = [UIColor colorWithRed:1.0 green:1.0 blue:1.0 alpha:0.1];
 
    CGRect topHalf = CGRectMake(rect.origin.x, rect.origin.y, rect.size.width, rect.size.height/2);
 
    drawLinearGradient(context, topHalf, glossColor1.CGColor, glossColor2.CGColor);
}

Ok so this routine is responsible for drawing a gradient over a rectangle from a start to end color, and then adding a gloss to the top half.

To draw the gradient, you call the routine you wrote earlier.

Then to draw the gloss, you just draw another gradient on top of that, from pretty-transparent (white with 0.35 alpha) to very transparent (white with 0.1 alpha).

Pretty simple eh? Let’s plug it in and see how it looks. Go back to CustomHeader.m and add the following to the bottom of drawRect:

-(void) drawRect:(CGRect)rect
{
    ... 
    drawGlossAndGradient(context, self.coloredBoxRect, self.lightColor.CGColor, self.darkColor.CGColor); 
 
    CGContextSetStrokeColorWithColor(context, self.darkColor.CGColor);
    CGContextSetLineWidth(context, 1.0);    
    CGContextStrokeRect(context, rectFor1PxStroke(self.coloredBoxRect));
}

Here you call your new routine, and then draw a 1 pixel stroke around the rect with the dark color, just like you learned how to do in the last Core Graphics tutorial.

Build and run the app, and check out the results:

Headers with Gloss

I think it looks pretty cool, how about you?

Where To Go From Here?

Here is a sample project with all of the code you’ve developed in this tutorial series so far.

At this point you should be eager to draw your own stuff with Core Graphics – think of how much you can do already!

And good news – more yet to come in this series! In the next tutorial you’ll wrap up this cool table view with your footer, which involves learning how to draw arcs with Core Graphics – and put on the finishing touches!

Ray Wenderlich

Ray is an indie software developer currently focusing on iPhone and iPad development, and the administrator of this site. He’s the founder of a small iPhone development studio called Razeware, and is passionate both about making apps and teaching others the techniques to make them.

When Ray’s not programming, he’s probably playing video games, role playing games, or board games.

User Comments

3 Comments

  • Hi ,

    I found code crashing while setting light and dark colour.
    ex.

    Code: Select all
    CGContextSetFillColorWithColor(context, self.lightColor.CGColor);


    For which I changed the autorelease light colour initialisation to a allocated variable.

    Code: Select all
    -(id) init{
        self=[super init];
        if(self){
            self.backgroundColor = [UIColor clearColor];
            self.opaque = NO;
            _titleLabel = [[UILabel alloc] init];
            _titleLabel.textAlignment = NSTextAlignmentCenter;
            _titleLabel.opaque = NO;
            _titleLabel.backgroundColor = [UIColor clearColor];
            _titleLabel.font = [UIFont boldSystemFontOfSize:20.0];
            _titleLabel.textColor = [UIColor whiteColor];
            _titleLabel.shadowColor = [UIColor colorWithRed:0 green:0 blue:0 alpha:0.5];
            _titleLabel.shadowOffset = CGSizeMake(0, -1);
            [self addSubview:_titleLabel];
           _lightColor = [[UIColor alloc] initWithRed:105.0f/255.0f green:179.0f/255.0f blue:216.0f/255.0f alpha:1.0];[list=][/list]
              _darkColor = [[UIColor alloc] initWithRed:21.0/255.0 green:92.0/255.0 blue:136.0/255.0 alpha:1.0];
        }
        return self;
    }


    Can you explain bit on blur value while setting shadow colour,I found in the documents that there is no range defined for it.
    nerry9
  • Hi,
    how to zoom the simulator?
    gerry1218
  • nerry9 wrote:Hi ,

    I found code crashing while setting light and dark colour.
    ex.

    Code: Select all
    CGContextSetFillColorWithColor(context, self.lightColor.CGColor);


    Change init to initWithFrame: and the original code will work correctly. :)

    Code: Select all

    - (id)initWithFrame:(CGRect)frame
    {
        self = [super initWithFrame:frame];
        if (self) {
                         // enter initialization code here
             }
       return self;
    }


    I don't have an answer for your second question.
    bassplayinMacFiend

Other Items of Interest

Ray's Monthly Newsletter

Sign up to receive a monthly newsletter with my favorite dev links, and receive a free epic-length tutorial as a bonus!

Advertise with Us!

Vote for Our Next Tutorial!

Every week, we alternate between Gaming and Non-Gaming tutorial votes. This week: Non-Gaming!

    Loading ... Loading ...

Last week's winner: How To Make a Tower Defense Game with Swift.

Suggest a Tutorial - Past Results

Hang Out With Us!

Every month, we have a free live Tech Talk - come hang out with us!


Coming up in December: The Great CALayer Tour

Sign Up - December

Our Books

Our Team

Tutorial Team

  • Matt Galloway
  • Corinne Krych
  • Sam Davies

... 59 total!

Update Team

... 13 total!

Editorial Team

  • Matt Galloway

... 17 total!

Code Team

  • Orta Therox

... 3 total!

Subject Matter Experts

  • Richard Casey

... 4 total!