Fun with layers

In this post, I’ll explain how to add a border, rounded corners, and drop shadow to any UIView using some simple CALayer properties.  I’m not a CALayer guru, but these few tricks from the layer world are particularly nice to know about.

These properties are present for every UIView, since every view is actually drawn using an underlying CALayer object owned by the UIView.  You can do a lot without even knowing about CALayers because UIViews encapsulate a lot of their functionality.  These properties, however, are useful pieces that are not available directly through the UIView interface.

To use these properties, you need to include the QuartzCore header:

#import <QuartzCore/QuartzCore.h>

Borders

To get a border, just set the borderColor and borderWidth properties of the layer, for example:

label.layer.borderColor = [UIColor blueColor].CGColor;
label.layer.borderWidth = 1;

The borderColor is a CGColorRef, which you can easily extract from any UIColor as in the above example, which generates a border like this:

The border is just inside the frame of the view.  Fractional values are allowed for the borderWidth as well.

Corners

You can create rounded corners with code like this:

label.layer.cornerRadius = 20;
label.layer.borderColor = [UIColor grayColor].CGColor;
label.layer.borderWidth = 3;

just the cornerRadius property is needed; I’ve also set the border to show how these properties work together:

As you can see in the example screenshot, the backgroundColor of the UIView is also restricted by the corner radius.  You need to have clipsToBounds set to YES on your UIView for rounded corners to work.

Shadows

You can also create a drop shadow that will be based on the alpha component of whatever is drawn in your view.  Often this will result in a shadow just around the edges of the view.  This example code on a UILabel:

label.layer.shadowColor = [UIColor blackColor].CGColor;
label.layer.shadowOpacity = 1.0;
label.layer.shadowRadius = 5.0;
label.layer.shadowOffset = CGSizeMake(0, 3);
label.clipsToBounds = NO;

results in this appearance:

In this case, you need clipsToBounds to be NO in order for a shadow outside the frame of your view to show up.  Next I’ll show you how you can actually combine rounded corners and drop shadows, since I’m sure that’s what you really want to do now.

All together

Let’s say you want to present an image with a border, rounded corners, and a drop shadow.  The obvious problem from the above explanations is that clipsToBounds needs to be YES for the rounded corners to work and NO for the drop shadows.  What’s a coder to do?

We can get around this apparent paradox by using the fact that the CALayer treats its own background color (which may be image-based) differently than the UIView‘s background color.  Specifically, we can set clipsToBounds to NO and still achieve rounded corners by using direct properties of the layer instead of the UIView.  This code:

UIView *imgView = [[[UIView alloc] initWithFrame:imgFrame] autorelease];
imgView.backgroundColor = [UIColor clearColor];
UIImage *image = [UIImage imageNamed:@"mandel.png"];
imgView.layer.backgroundColor = [UIColor colorWithPatternImage:image].CGColor;

// Rounded corners.
imgView.layer.cornerRadius = 10;

// A thin border.
imgView.layer.borderColor = [UIColor blackColor].CGColor;
imgView.layer.borderWidth = 0.3;

// Drop shadow.
imgView.layer.shadowColor = [UIColor blackColor].CGColor;
imgView.layer.shadowOpacity = 1.0;
imgView.layer.shadowRadius = 7.0;
imgView.layer.shadowOffset = CGSizeMake(0, 4);

Generates the image on the right, using the left image as the source (mandel.png):

Reference

I originally learned about this stuff from this blog post.

CALayer class reference

9 Comments

  1. Kamol
    Posted August 17, 2010 at 2:49 am | Permalink

    It’s really helpfull. thank you so much.

  2. anon
    Posted November 9, 2010 at 5:26 am | Permalink

    please make your code copy-able, images suck big times.

  3. Posted January 3, 2011 at 10:11 pm | Permalink

    Thanks

    Nice code.

    Can I create this type of effect on UIView
    check this link
    http://www.flickr.com/photos/24313234@N07/5323036634/

    please write the code for that

  4. Posted January 5, 2011 at 2:17 pm | Permalink

    Ok, @umar, you’re asking about making a sort of torn-paper-edge effect, with correspondingly torn-looking shadow, on an arbitrary UIView, if I correctly understand the image link.

    Three things:

    (1) I probably won’t actually write the code for you, sorry. But I’m happy to try to answer your question on how to do it.

    (2) You _can_ do something like this on any UIView using CALayer’s mask property for the torn edges, and using a UIBezierPath with CALayer’s shadowPath for the shadow.

    (3) It will be a relatively slow-to-draw UIView. If you want to do any animations involving this UIView, it might be faster to render it as a static image. Things are always smoother if you can avoid transparency, and drop shadows have lots of transparency. If you know you’re going to have a solid color or stationary background beneath the drop shadow, you can take advantage of that by rendering everything in advance, so it is actually a solid image (such as in a UIImageView) but appears transparent. If you want to render it using Quartz, I think you can use CGContextBeginTransparencyLayer to help make a drop-shadow that matches a mask. For the mask in Quartz, you can use CGContextClip after setting the context’s path to have the border shape you want.

    References for the Quartz functions:

    http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/dq_trans_layers/dq_trans_layers.html

    http://developer.apple.com/library/mac/#documentation/GraphicsImaging/Reference/CGContext/Reference/reference.html

  5. Posted January 7, 2011 at 8:35 am | Permalink

    Thanks

    @Tyler , Please tell me how can I torn edges of UIView with UIBezierPath.

    Please write code for this.

    I am new developer in iPhone.

  6. lucio
    Posted March 10, 2011 at 11:45 pm | Permalink

    Hi Tyler,
    even if old, this is a very good tutorial. I followed it and I almost get your result, the image I use in colorWithPatternImage: gets displayed as inverted on the y axis. Is it a normal behavior?

  7. Posted March 11, 2011 at 12:26 am | Permalink

    Hi @lucio,

    This is a common problem, having images flipped in the y-axis. This happens because UIKit code (UIGraphicsX functions and UIImage stuff) all use a y-axis-points-down coordinate system (aka ULO = upper-left-hand origin), while Core Graphics functions and some lower-level things based on OpenGL use a y-axis-points-up coordinate system (aka LLO = lower-left-hand origin). I don’t know exactly what your code looks like, but if you use any CGImageRef objects, for example, your image can appear inverted.

    It’s usually easy to fix this. You just need to use a transform that “scales” the image by -1 in the y direction. The exact code will depend on your situation, but this link should be very helpful:

    http://developer.apple.com/library/ios/#documentation/2DDrawing/Conceptual/DrawingPrintingiOS/GraphicsDrawingOverview/GraphicsDrawingOverview.html

    (The most helpful section at that link is “Flipping the Default Coordinate System”.)

  8. lucio
    Posted March 11, 2011 at 4:06 am | Permalink

    Thank you Tyler, the code is exactly what you posted here, so I’m asking if mandel.png is flipped as the original image. I’ll have a look at Apple doc, thanks again

  9. lucio
    Posted March 12, 2011 at 6:43 am | Permalink

    I so the docs, but I’m not using a CGContextRef directly, I just put your code in the init method of UIButton subclass. Does it happen also to you with the madel.png image? Sorry to bother you ^^

Post a Comment

Your email is never published nor shared. Required fields are marked *

*
*