122

In my application - there are four buttons named as follows:

  • Top - left
  • Bottom - left
  • Top - right
  • Bottom - right

Above the buttons there is an image view (or a UIView).

Now, suppose a user taps on - top - left button. Above image / view should be rounded at that particular corner.

I am having some difficulty in applying rounded corners to the UIView.

Right now I am using the following code to apply the rounded corners to each view:

    // imgVUserImg is a image view on IB.
    imgVUserImg.image=[UIImage imageWithData:[NSData dataWithContentsOfURL:[NSURL URLWithString:@"any Url Here"];
    CALayer *l = [imgVUserImg layer];
    [l setMasksToBounds:YES];
    [l setCornerRadius:5.0];  
    [l setBorderWidth:2.0];
    [l setBorderColor:[[UIColor darkGrayColor] CGColor]];

Above code is applying the roundness to each of corners of supplied View. Instead I just wanted to apply roundness to selected corners like - top / top+left / bottom+right etc.

Is it possible? How?

Wyetro
  • 7,986
  • 9
  • 42
  • 61
Sagar R. Kothari
  • 23,587
  • 48
  • 158
  • 226

14 Answers14

358

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 path
CAShapeLayer *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 layer
imageView.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 view
UIView *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 layer
CAShapeLayer *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 layer
CALayer *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];
Community
  • 1
  • 1
Stuart
  • 34,797
  • 19
  • 93
  • 135
  • 1
    Nice one, this helped me a lot on grouped UITableView cell's selectedBackgroundViews :-) – Luke47 Oct 27 '11 at 10:06
  • Anyway to add a drop shadow to this view after you've rounded the corners? Attempted the following without success. ` self.layer.masksToBounds = NO; self.layer.shadowColor = [UIColor blackColor].CGColor; self.layer.shadowRadius = 3; self.layer.shadowOffset = CGSizeMake(-3, 3); self.layer.shadowOpacity = 0.8; self.layer.shouldRasterize = YES; ` – Chris Wagner Nov 04 '11 at 22:13
  • 1
    @ChrisWagner: See my edit, with regards to applying a shadow to the rounded view. – Stuart Nov 05 '11 at 00:37
  • @StuDev excellent! I had gone with a subview for the shadow view but this is much nicer! Didn't think to add sub layers like this. Thanks! – Chris Wagner Nov 07 '11 at 19:28
  • Why does my image becomes white, then when I scroll the UITableView and the cell is recreated - it works! Why? – Fernando Redondo Nov 16 '11 at 14:34
  • Works as advertised in 5 minutes after fruitlessly trying to generate bitmaps from vector paths to use as layer masks :) – Steven Kramer Dec 29 '11 at 09:59
  • In case anyone is trying to round two corners in one mask, like me, use the OR operator like this: byRoundingCorners:UIRectCornerTopLeft | UIRectCornerTopRight – spiralstairs Dec 29 '11 at 19:46
  • `CALayer`'s `mask` property is not directly animatable, however you can easily animate the maskLayer's `path` property. A `CABasicAnimation` should work fine. The compiler will show a warning for the `fromValue` and `toValue` - suppress it by casting the `CGPath` to `id`. – Stuart Feb 04 '12 at 06:20
  • This was an excellent answer. Thank you for taking the time to be so thorough – Mark Struzinski Mar 30 '12 at 03:00
  • Another upvote, just added mine to say that as of March 2013 this works in iOS 6.1.2 – Cocoadelica Mar 07 '13 at 11:36
  • IF anybody is confused about ORing you can use following code. UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:_tableView.bounds byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight cornerRadii:CGSizeMake(10.0, 10.0)]; CAShapeLayer *maskLayer = [CAShapeLayer layer]; maskLayer.frame = _tableView.bounds; maskLayer.path = maskPath.CGPath; _tableView.layer.mask = maskLayer; – Desert Rose May 02 '13 at 11:08
  • @Luke47 can you please share how because ive been trying without success. Even after Oring my cells' right corners both top and bottom dont get rounded...plus the greyish boundary is also covered – Bushra Shahid Aug 08 '13 at 06:11
  • @Stuart: is there any way to make this mask auto-resize itself? I'm trying to use this code to round off only the upper-left corner of a view. The code works, but only with whatever size the view is when I call this code. If I then make my view larger I can't see it, because the new area is outside of the mask. – MusiGenesis Jan 23 '14 at 02:32
  • @MusiGenesis `UIView`s autoresizes subviews according to their `autoresizingMask`, however sublayers that don't back such a `UIView` aren't autoresized, so you'll have to do it manually. It's easy though - whenever you resize your view (or in `layoutSubviews` if your view is subclassed, or even in your view controller's `viewWill/DidLayoutSubviews` method if appropriate) get a reference to the mask (`CAShapeLayer *mask = (CAShapeLayer *)view.layer.mask;`), create a new `UIBezierPath` using the view's new bounds & change the path (`mask.path = newPath.CGPath;` `mask.frame = view.bounds;`). – Stuart Jan 23 '14 at 08:12
  • The doc doesn't explicitly say this, but when you set `layer.mask`, this `layer` becomes the `mask`'s parent. That is why when we set `mask.frame` for rounded corners we set it to the `layer.bounds`: mask will cover the entire area of the parent layer, in parent's own coordinate. – HuaTham Jan 13 '17 at 19:53
44

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.

Community
  • 1
  • 1
nevan king
  • 108,735
  • 42
  • 196
  • 237
  • Doesn't work as expected with views whose size might change. Example: a UILabel. Setting the mask path and then changing the size by setting text to it will keep the old mask. Will need subclassing and overloading the drawRect. – user3099609 Oct 01 '14 at 14:24
18

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];
itsaboutcode
  • 21,936
  • 42
  • 103
  • 153
  • When using this solution on `UITableView`-subviews (e.g. `UITableViewCell`s or `UITableViewHeaderFooterView`s) it **results in bad smoothness when scrolling**. Another approach for that use is [this](http://stackoverflow.com/a/7487400/2471006) solution with a better performance (it adds a cornerRadius to all corners). To 'show' only specific corners rounded (like top and bottom right corners) I added a subview with a negative value on its `frame.origin.x` and assigned the cornerRadius to its layer. If someone found a better solution, I'm interested. – anneblue Mar 04 '14 at 12:08
13

In iOS 11, we can now round some corners only

let view = UIView()

view.clipsToBounds = true
view.layer.cornerRadius = 8
view.layer.maskedCorners = [.layerMaxXMaxYCorner, .layerMinXMaxYCorner]
onmyway133
  • 38,911
  • 23
  • 231
  • 237
8

CALayer extension with Swift 3+ syntax

extension CALayer {

    func round(roundedRect rect: CGRect, byRoundingCorners corners: UIRectCorner, cornerRadii: CGSize) -> Void {
        let bp = UIBezierPath(roundedRect: rect, byRoundingCorners: corners, cornerRadii: cornerRadii)
        let sl = CAShapeLayer()
        sl.frame = self.bounds
        sl.path = bp.cgPath
        self.mask = sl
    }
}

It can be used like:

let layer: CALayer = yourView.layer
layer.round(roundedRect: yourView.bounds, byRoundingCorners: [.bottomLeft, .topLeft], cornerRadii: CGSize(width: 5, height: 5))
Maverick
  • 2,795
  • 1
  • 30
  • 36
5

Stuarts example for rounding specific corners works great. If you want to round multiple corners like top left and right this is how to do it

// Create the path (with only the top-left corner rounded)
UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:imageview
                                               byRoundingCorners:UIRectCornerTopLeft|UIRectCornerTopRight
                                                     cornerRadii:CGSizeMake(10.0, 10.0)];

// Create the shape layer and set its path
CAShapeLayer *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 layer
imageview.layer.mask = maskLayer; 
aZtraL-EnForceR
  • 1,744
  • 2
  • 16
  • 19
  • When using this solution on `UITableView`-subviews (e.g. `UITableViewCell`s or `UITableViewHeaderFooterView`s) it **results in bad smoothness when scrolling**. Another approach for that use is [this](http://stackoverflow.com/a/7487400/2471006) solution with a better performance (it adds a cornerRadius to all corners). To 'show' only specific corners rounded (like top and bottom right corners) I added a subview with a negative value on its `frame.origin.x` and assigned the cornerRadius to its layer. If someone found a better solution, I'm interested. – anneblue Mar 04 '14 at 11:56
5

Thanks for sharing. Here I'd like to share the solution on swift 2.0 for further reference on this issue. (to conform the UIRectCorner's protocol)

let mp = UIBezierPath(roundedRect: cell.bounds, byRoundingCorners: [.bottomLeft, .TopLeft], cornerRadii: CGSize(width: 10, height: 10))
let ml = CAShapeLayer()
ml.frame = self.bounds
ml.path = mp.CGPath
self.layer.mask = ml
Jog Dan
  • 171
  • 3
  • 8
4

there is an easier and faster answer that may work depending on your needs and also works with shadows. you can set maskToBounds on the superlayer to true, and offset the child layers so that 2 of their corners are outside the superlayer bounds, effectively cutting the rounded corners on 2 sides away.

of course this only works when you want to have only 2 rounded corners on the same side and the content of the layer looks the same when you cut off a few pixels from one side. works great for having bar charts rounded only on the top side.

user1259710
  • 839
  • 1
  • 9
  • 11
3

See this related question. You'll have to draw your own rectangle to a CGPath with some rounded corners, add the CGPath to your CGContext and then clip to it using CGContextClip.

You can also draw the rounded rect with alpha values to an image and then use that image to create a new layer which you set as your layer's mask property (see Apple's documentation).

Community
  • 1
  • 1
MrMage
  • 6,995
  • 2
  • 33
  • 64
2

Rounding only some corners won't play nice with auto resizing or auto layout.

So another option is to use regular cornerRadius and hide the corners you don't want under another view or outside its superview bounds making sure it is set to clip its contents.

Rivera
  • 10,182
  • 3
  • 49
  • 96
2

Half a decade late, but I think the current way people do this isn't 100% right. Many people have had the issue that using the UIBezierPath + CAShapeLayer method interferes with Auto-layout, especially when it is set on the Storyboard. No answers go over this, so I decided to add my own.

There is a very easy way to circumvent that: Draw the rounded corners in the drawRect(rect: CGRect) function.

For example, if I wanted top rounded corners for a UIView, I'd subclass UIView and then use that subclass wherever appropriate.

import UIKit

class TopRoundedView: UIView {

    override func drawRect(rect: CGRect) {
        super.drawRect(rect)

        var maskPath = UIBezierPath(roundedRect: self.bounds, byRoundingCorners: UIRectCorner.TopLeft | UIRectCorner.TopRight, cornerRadii: CGSizeMake(5.0, 5.0))

        var maskLayer = CAShapeLayer()
        maskLayer.frame = self.bounds
        maskLayer.path = maskPath.CGPath

        self.layer.mask = maskLayer
    }
}

This is the best way to conquer the issue and doesn't take any time at all to adapt to.

David
  • 6,021
  • 8
  • 39
  • 85
  • 1
    This isn't the right way to solve the problem. You should only override `drawRect(_:)` if you're doing custom drawing in your view (e.g. with Core Graphics), otherwise even an empty implementation can impact performance. Creating a new mask layer every time is unnecessary too. All you actually need to do is update the mask layer's `path` property when the view's bounds change, and the place to do that is within an override of the `layoutSubviews()` method. – Stuart Mar 08 '16 at 11:57
1

To add to to the answer and the addition, I created a simple, reusable UIView in Swift. Depending on your use case, you might want to make modifications (avoid creating objects on every layout etc.), but I wanted to keep it as simple as possible. The extension allows you to apply this to other view's (ex. UIImageView) easier if you do not like subclassing.

extension UIView {

    func roundCorners(_ roundedCorners: UIRectCorner, toRadius radius: CGFloat) {
        roundCorners(roundedCorners, toRadii: CGSize(width: radius, height: radius))
    }

    func roundCorners(_ roundedCorners: UIRectCorner, toRadii cornerRadii: CGSize) {
        let maskBezierPath = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: roundedCorners,
            cornerRadii: cornerRadii)
        let maskShapeLayer = CAShapeLayer()
        maskShapeLayer.frame = bounds
        maskShapeLayer.path = maskBezierPath.cgPath
        layer.mask = maskShapeLayer
    }
}

class RoundedCornerView: UIView {

    var roundedCorners: UIRectCorner = UIRectCorner.allCorners
    var roundedCornerRadii: CGSize = CGSize(width: 10.0, height: 10.0)

    override func layoutSubviews() {
        super.layoutSubviews()
        roundCorners(roundedCorners, toRadii: roundedCornerRadii)
    }
}

Here's how you would apply it to a UIViewController:

class MyViewController: UIViewController {

    private var _view: RoundedCornerView {
        return view as! RoundedCornerView
    }

    override func loadView() {
        view = RoundedCornerView()
    }

    override func viewDidLoad() {
        super.viewDidLoad()
        _view.roundedCorners = [.topLeft, .topRight]
        _view.roundedCornerRadii = CGSize(width: 10.0, height: 10.0)
    }
}
Community
  • 1
  • 1
kgaidis
  • 10,753
  • 4
  • 58
  • 76
0

Wrapping up Stuart's answer, you can have rounding corner method as the following:

@implementation UIView (RoundCorners)

- (void)applyRoundCorners:(UIRectCorner)corners radius:(CGFloat)radius {
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];

    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = self.bounds;
    maskLayer.path = maskPath.CGPath;

    self.layer.mask = maskLayer;
}

@end

So to apply rounding corner, you simply do:

[self.imageView applyRoundCorners:UIRectCornerTopRight|UIRectCornerTopLeft radius:10];
Willy
  • 9,171
  • 5
  • 24
  • 25
0

I'd suggest defining a layer's mask. The mask itself should be a CAShapeLayer object with a dedicated path. You can use the next UIView extension (Swift 4.2):

extension UIView {
    func round(corners: UIRectCorner, with radius: CGFloat) {
        let maskLayer = CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.path = UIBezierPath(
            roundedRect: bounds,
            byRoundingCorners: corners,
            cornerRadii: CGSize(width: radius, height: radius)
        ).cgPath
        layer.mask = maskLayer
   }
}
sgl0v
  • 1,117
  • 8
  • 13