Rounded UIView using CALayers - only some corners - How? Rounded UIView using CALayers - only some corners - How? xcode xcode

Rounded UIView using CALayers - only some corners - How?


Starting in iOS 3.2, you can use the functionality of UIBezierPaths to create an out-of-the-box rounded rect (where only corners you specify are rounded). You can then use this as the path of a CAShapeLayer, and use this as a mask for your view's layer:

// Create the path (with only the top-left corner rounded)UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageView.bounds                                                byRoundingCorners:UIRectCornerTopLeft                                                     cornerRadii:CGSizeMake(10.0, 10.0)];// Create the shape layer and set its pathCAShapeLayer *maskLayer = [CAShapeLayer layer];maskLayer.frame = imageView.bounds;maskLayer.path = maskPath.CGPath;// Set the newly created shape layer as the mask for the image view's layerimageView.layer.mask = maskLayer;

And that's it - no messing around manually defining shapes in Core Graphics, no creating masking images in Photoshop. The layer doesn't even need invalidating. Applying the rounded corner or changing to a new corner is as simple as defining a new UIBezierPath and using its CGPath as the mask layer's path. The corners parameter of the bezierPathWithRoundedRect:byRoundingCorners:cornerRadii: method is a bitmask, and so multiple corners can be rounded by ORing them together.


EDIT: Adding a shadow

If you're looking to add a shadow to this, a little more work is required.

Because "imageView.layer.mask = maskLayer" applies a mask, a shadow will not ordinarily show outside of it. The trick is to use a transparent view, and then add two sublayers (CALayers) to the view's layer: shadowLayer and roundedLayer. Both need to make use of the UIBezierPath. The image is added as the content of roundedLayer.

// Create a transparent viewUIView *theView = [[UIView alloc] initWithFrame:theFrame];[theView setBackgroundColor:[UIColor clearColor]];// Create the path (with only the top-left corner rounded)UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:theView.bounds                                                byRoundingCorners:UIRectCornerTopLeft                                                     cornerRadii:CGSizeMake(10.0f, 10.0f)];// Create the shadow layerCAShapeLayer *shadowLayer = [CAShapeLayer layer];[shadowLayer setFrame:theView.bounds];[shadowLayer setMasksToBounds:NO];[shadowLayer setShadowPath:maskPath.CGPath];// ...// Set the shadowColor, shadowOffset, shadowOpacity & shadowRadius as required// ...// Create the rounded layer, and mask it using the rounded mask layerCALayer *roundedLayer = [CALayer layer];[roundedLayer setFrame:theView.bounds];[roundedLayer setContents:(id)theImage.CGImage];CAShapeLayer *maskLayer = [CAShapeLayer layer];[maskLayer setFrame:theView.bounds];[maskLayer setPath:maskPath.CGPath];roundedLayer.mask = maskLayer;// Add these two layers as sublayers to the view[theView.layer addSublayer:shadowLayer];[theView.layer addSublayer:roundedLayer];


I used the answer over at How do I create a round cornered UILabel on the iPhone? and the code from How is a rounded rect view with transparency done on iphone? to make this code.

Then I realized I'd answered the wrong question (gave a rounded UILabel instead of UIImage) so I used this code to change it:

http://discussions.apple.com/thread.jspa?threadID=1683876

Make an iPhone project with the View template. In the view controller, add this:

- (void)viewDidLoad{    CGRect rect = CGRectMake(10, 10, 200, 100);    MyView *myView = [[MyView alloc] initWithFrame:rect];    [self.view addSubview:myView];    [super viewDidLoad];}

MyView is just a UIImageView subclass:

@interface MyView : UIImageView{}

I'd never used graphics contexts before, but I managed to hobble together this code. It's missing the code for two of the corners. If you read the code, you can see how I implemented this (by deleting some of the CGContextAddArc calls, and deleting some of the radius values in the code. The code for all corners is there, so use that as a starting point and delete the parts that create corners you don't need. Note that you can make rectangles with 2 or 3 rounded corners too if you want.

The code's not perfect, but I'm sure you can tidy it up a little bit.

static void addRoundedRectToPath(CGContextRef context, CGRect rect, float radius, int roundedCornerPosition){    // all corners rounded    //  CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);    //  CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);    //  CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius,     //                  radius, M_PI / 4, M_PI / 2, 1);    //  CGContextAddLineToPoint(context, rect.origin.x + rect.size.width - radius,     //                          rect.origin.y + rect.size.height);    //  CGContextAddArc(context, rect.origin.x + rect.size.width - radius,     //                  rect.origin.y + rect.size.height - radius, radius, M_PI / 2, 0.0f, 1);    //  CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y + radius);    //  CGContextAddArc(context, rect.origin.x + rect.size.width - radius, rect.origin.y + radius,     //                  radius, 0.0f, -M_PI / 2, 1);    //  CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);    //  CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius,     //                  -M_PI / 2, M_PI, 1);    // top left    if (roundedCornerPosition == 1) {        CGContextMoveToPoint(context, rect.origin.x, rect.origin.y + radius);        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height - radius);        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + rect.size.height - radius,                         radius, M_PI / 4, M_PI / 2, 1);        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width,                                 rect.origin.y + rect.size.height);        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y);    }       // bottom left    if (roundedCornerPosition == 2) {        CGContextMoveToPoint(context, rect.origin.x, rect.origin.y);        CGContextAddLineToPoint(context, rect.origin.x, rect.origin.y + rect.size.height);        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width,                                 rect.origin.y + rect.size.height);        CGContextAddLineToPoint(context, rect.origin.x + rect.size.width, rect.origin.y);        CGContextAddLineToPoint(context, rect.origin.x + radius, rect.origin.y);        CGContextAddArc(context, rect.origin.x + radius, rect.origin.y + radius, radius,                         -M_PI / 2, M_PI, 1);    }    // add the other corners here    CGContextClosePath(context);    CGContextRestoreGState(context);}-(UIImage *)setImage{    UIImage *img = [UIImage imageNamed:@"my_image.png"];    int w = img.size.width;    int h = img.size.height;    CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();    CGContextRef context = CGBitmapContextCreate(NULL, w, h, 8, 4 * w, colorSpace, kCGImageAlphaPremultipliedFirst);    CGContextBeginPath(context);    CGRect rect = CGRectMake(0, 0, w, h);    addRoundedRectToPath(context, rect, 50, 1);    CGContextClosePath(context);    CGContextClip(context);    CGContextDrawImage(context, rect, img.CGImage);    CGImageRef imageMasked = CGBitmapContextCreateImage(context);    CGContextRelease(context);    CGColorSpaceRelease(colorSpace);    [img release];    return [UIImage imageWithCGImage:imageMasked];}

alt text http://nevan.net/skitch/skitched-20100224-092237.png

Don't forget that you'll need to get the QuartzCore framework in there for this to work.


I have used this code in many places in my code and it works 100% correctly. You can change any corder by changed one property "byRoundingCorners:UIRectCornerBottomLeft"

UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:view.bounds byRoundingCorners:UIRectCornerBottomLeft cornerRadii:CGSizeMake(10.0, 10.0)];                CAShapeLayer *maskLayer = [[CAShapeLayer alloc] init];                maskLayer.frame = view.bounds;                maskLayer.path = maskPath.CGPath;                view.layer.mask = maskLayer;                [maskLayer release];