54
votes

I need to create a rectangle that have just two rounded corners in swift (Objective C code also ok).

At the moment my code is creating two rectangles with

CGPathCreateWithRoundedRect(CGRectMake(0, 0, 30, 60), 5, 5, nil);

and

CGPathCreateWithRoundedRect(CGRectMake(0, 0, 30, 60), 0, 0, nil);

and merging them (to have two right angle corners and two rounded ones) but I am not happy with the code and I am pretty sure there should be much better ways to do it.

I am new to iOS and graphical development and swift.

16
developer.apple.com/library/ios/documentation/UIKit/Reference/…: bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:matt
As usual there have been *many small changes in Swift, eg capitalization of constants, etc etc. Suggest scroll down to newest answer.Fattie
Check out my answer here, it'll cover everything:- https://stackoverflow.com/a/68342661/9863222Vipul Kumar

16 Answers

56
votes

In Swift 2.3 you could do so by

let maskPath = UIBezierPath(roundedRect: anyView.bounds,
            byRoundingCorners: [.BottomLeft, .BottomRight],
            cornerRadii: CGSize(width: 10.0, height: 10.0))

let shape = CAShapeLayer()
shape.path = maskPath.CGPath
view.layer.mask = shape

In Objective-C you could use the UIBezierPath class method

bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:

example implementation-

// set the corner radius to the specified corners of the passed container
- (void)setMaskTo:(UIView*)view byRoundingCorners:(UIRectCorner)corners
{
    UIBezierPath *rounded = [UIBezierPath bezierPathWithRoundedRect:view.bounds
                                                  byRoundingCorners:corners
                                                        cornerRadii:CGSizeMake(10.0, 10.0)];
    CAShapeLayer *shape = [[CAShapeLayer alloc] init];
    [shape setPath:rounded.CGPath];
    view.layer.mask = shape;
}

and call the above method as-

[self setMaskTo:anyView byRoundingCorners:UIRectCornerBottomLeft | UIRectCornerBottomRight];
68
votes

Update: See this answer below for Swift 4 / iOS 11 which is much, much easier


Here's a quick Swift 3 extension you can use to do rounding and optional borders.

Note: if you're using autolayout, you may need to call this in one of the view lifecycle callbacks like viewDidLayoutSubviews or layoutSubviews after the view has been constrained.

import UIKit

extension UIView {
    
    /**
     Rounds the given set of corners to the specified radius
     
     - parameter corners: Corners to round
     - parameter radius:  Radius to round to
     */
    func round(corners: UIRectCorner, radius: CGFloat) {
        _ = _round(corners: corners, radius: radius)
    }
    
    /**
     Rounds the given set of corners to the specified radius with a border
     
     - parameter corners:     Corners to round
     - parameter radius:      Radius to round to
     - parameter borderColor: The border color
     - parameter borderWidth: The border width
     */
    func round(corners: UIRectCorner, radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        let mask = _round(corners: corners, radius: radius)
        addBorder(mask: mask, borderColor: borderColor, borderWidth: borderWidth)
    }
    
    /**
     Fully rounds an autolayout view (e.g. one with no known frame) with the given diameter and border
     
     - parameter diameter:    The view's diameter
     - parameter borderColor: The border color
     - parameter borderWidth: The border width
     */
    func fullyRound(diameter: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        layer.masksToBounds = true
        layer.cornerRadius = diameter / 2
        layer.borderWidth = borderWidth
        layer.borderColor = borderColor.cgColor;
    }
    
}

private extension UIView {
    
    @discardableResult func _round(corners: UIRectCorner, radius: CGFloat) -> CAShapeLayer {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
        return mask
    }
    
    func addBorder(mask: CAShapeLayer, borderColor: UIColor, borderWidth: CGFloat) {
        let borderLayer = CAShapeLayer()
        borderLayer.path = mask.path
        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.strokeColor = borderColor.cgColor
        borderLayer.lineWidth = borderWidth
        borderLayer.frame = bounds
        layer.addSublayer(borderLayer)
    }
    
}
59
votes

Swift 4+, iOS 11+

If you already have a UIView named myView referenced as an IBOutlet, try adding the following two lines in ViewDidLoad() or wherever it's being loaded:

myView.layer.cornerRadius = 10
myView.layer.maskedCorners = [.layerMinXMaxYCorner, .layerMaxXMaxYCorner]

You can change the array [] to any combination of MinX, MinY, MaxX, and MaxY to select the desired corners. The above example rounds the bottom two corners.

This is just another approach, can be a bit simpler depending on your design.

26
votes

Swift 3 - Useful UIView extension when you need to round specific corners of some views:

extension UIView {
  func round(corners: UIRectCorner, radius: CGFloat) {
    let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
    let mask = CAShapeLayer()
    mask.path = path.cgPath
    self.layer.mask = mask
  }
}

then just use it like this:

someView.round(corners: [.topLeft, .topRight], radius: 5)
11
votes

Building on top of Sanjay's excellent answer, I wrote a quick CALayer extension for Swift 2.3, in case you need to do this sort of "only round some corners" thing more than once.

extension CALayer {
  func roundCorners(corners: UIRectCorner, radius: CGFloat) {
    let maskPath = UIBezierPath(roundedRect: bounds,
                                byRoundingCorners: corners,
                                cornerRadii: CGSize(width: radius, height: radius))

    let shape = CAShapeLayer()
    shape.path = maskPath.CGPath
    mask = shape
  }
}

Usage:

myView.layer.roundCorners([.TopLeft, .TopRight], radius: myCornerRadius)

Swift 3.0 (In this example the bounds came from the view not from the layer. Using the bounds from the view make this code to work with views in a UITableViewCell.):

func roundCorners(corners: UIRectCorner, radius: CGFloat, viewBounds: CGRect) {

    let maskPath = UIBezierPath(roundedRect: viewBounds,
                                byRoundingCorners: corners,
                                cornerRadii: CGSize(width: radius, height: radius))

    let shape = CAShapeLayer()
    shape.path = maskPath.cgPath
    mask = shape
}

Usage:

myView.layer.roundCorners(corners: [.topLeft, .topRight], radius: myCornerRadius, viewBounds: bounds)
9
votes

iOS 11+ Only | You can check iOS usage stats here

Explanation

Since the CACornerMask rawValue is an UInt you know that a CACornerMask rawValue is the sum of each CACornerMask.Element rawValue

More specifically:

  • TopLeft (layerMinXMinYCorner) = 1
  • TopRight (layerMaxXMinYCorner) = 2
  • BottomLeft (layerMinXMaxYCorner) = 4
  • BottomRight (layerMaxXMaxYCorner) = 8

So for example if you want top left and top right corners you can just type CACornerMask(rawValue: 3).


 Example

Below is a simple extension of UIView

extension UIView {
    enum Corner:Int {
        case bottomRight = 0,
        topRight,
        bottomLeft,
        topLeft
    }
    
    private func parseCorner(corner: Corner) -> CACornerMask.Element {
        let corners: [CACornerMask.Element] = [.layerMaxXMaxYCorner, .layerMaxXMinYCorner, .layerMinXMaxYCorner, .layerMinXMinYCorner]
        return corners[corner.rawValue]
    }
    
    private func createMask(corners: [Corner]) -> UInt {
        return corners.reduce(0, { (a, b) -> UInt in
            return a + parseCorner(corner: b).rawValue
        })
    }
    
    func roundCorners(corners: [Corner], amount: CGFloat = 5) {
        layer.cornerRadius = amount
        let maskedCorners: CACornerMask = CACornerMask(rawValue: createMask(corners: corners))
        layer.maskedCorners = maskedCorners
    }
}

You can use it this like:

let myRect = UIView(frame: CGRect(x: 0, y: 0, width: 200, height: 50))
myRect.roundCorners(corners: [.topRight, .topLeft])
8
votes

Here is what you do in Swift 2.0

var maskPath = UIBezierPath(roundedRect: anyView.bounds,
        byRoundingCorners: [.BottomLeft, .BottomRight],
        cornerRadii: CGSize(width: 10.0, height: 10.0))
8
votes

Up-to-date for 2021 ...

Please note that syntax/systems have changed a lot since this question was asked a long time ago!

enter image description here

import UIKit
@IBDesignable
class RoundedEnds: UIView {
    
    override func layoutSubviews() {
        super.layoutSubviews()
        setup()
    }
    
    func setup() {
        let r = self.bounds.size.height / 2
        let path = UIBezierPath(roundedRect: self.bounds, cornerRadius:r)
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
    }
}

For only some corners, just change the path line of code to:

enter image description here

    let path = UIBezierPath(
        roundedRect: self.bounds,
        byRoundingCorners: [.topLeft,.topRight],
        cornerRadii: CGSize(width: r, height: r))
4
votes

Swift 4:

let maskPath = UIBezierPath(
            roundedRect: view.bounds,
            byRoundingCorners: [.allCorners],
            cornerRadii: CGSize(width: 10.0, height: 10.0)
        )

let shape = CAShapeLayer()
shape.path = maskPath.cgPath

view.layer.mask = shape
3
votes

Updated iWasRobbed's answer to work with the Swift 3.0 GM version:

import UIKit

extension UIView {

    /**
     Rounds the given set of corners to the specified radius

     - parameter corners: Corners to round
     - parameter radius:  Radius to round to
     */
    func round(corners: UIRectCorner, radius: CGFloat) {
        _round(corners: corners, radius: radius)
    }

    /**
     Rounds the given set of corners to the specified radius with a border

     - parameter corners:     Corners to round
     - parameter radius:      Radius to round to
     - parameter borderColor: The border color
     - parameter borderWidth: The border width
     */
    func round(corners: UIRectCorner, radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        let mask = _round(corners: corners, radius: radius)
        addBorder(mask: mask, borderColor: borderColor, borderWidth: borderWidth)
    }

    /**
     Fully rounds an autolayout view (e.g. one with no known frame) with the given diameter and border

     - parameter diameter:    The view's diameter
     - parameter borderColor: The border color
     - parameter borderWidth: The border width
     */
    func fullyRound(diameter: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        layer.masksToBounds = true
        layer.cornerRadius = diameter / 2
        layer.borderWidth = borderWidth
        layer.borderColor = borderColor.cgColor;
    }

}

private extension UIView {

    @discardableResult func _round(corners: UIRectCorner, radius: CGFloat) -> CAShapeLayer {
        let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let mask = CAShapeLayer()
        mask.path = path.cgPath
        self.layer.mask = mask
        return mask
    }

    func addBorder(mask: CAShapeLayer, borderColor: UIColor, borderWidth: CGFloat) {
        let borderLayer = CAShapeLayer()
        borderLayer.path = mask.path
        borderLayer.fillColor = UIColor.clear.cgColor
        borderLayer.strokeColor = borderColor.cgColor
        borderLayer.lineWidth = borderWidth
        borderLayer.frame = bounds
        layer.addSublayer(borderLayer)
    }

}
2
votes
extension CACornerMask {

    public static var leftBottom     : CACornerMask { get { return .layerMinXMaxYCorner}}
    public static var rightBottom    : CACornerMask { get { return .layerMaxXMaxYCorner}}
    public static var leftTop        : CACornerMask { get { return .layerMaxXMinYCorner}}
    public static var rightTop       : CACornerMask { get { return .layerMinXMinYCorner}}
}

extension CALayer {

    func roundCorners(_ mask:CACornerMask,corner:CGFloat) {
        self.maskedCorners = mask
        self.cornerRadius = corner
    }
}

self.viewBack.layer.roundCorners([.leftBottom,.rightBottom], corner: 23)
1
votes

In summary, you can create pretty extension like this:

extension UIView {

    func roundCorners(_ corners: UIRectCorner, radius: Double) {
        let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
        let shape = CAShapeLayer()
        shape.path = maskPath.cgPath
        layer.mask = shape
    }

}

Use it like this:

view.roundCorners([.topRight, .bottomRight], radius: 10)

Here is all corners values:

  • .topLeft
  • .topRight
  • .bottomLeft
  • .bottomRight
1
votes
view.layer.cornerRadius = 10.0
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]

Best way to do it!

1
votes

Swift 5: For top-left and top-right round corners.

yourView.layer.cornerRadius = 12
yourView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
0
votes

Objective-C version of iWasRobbed's answer:

UIView+RoundCorners.h

#import <UIKit/UIKit.h>

@interface UIView (RoundCorners)

/**
 Rounds the given set of corners to the specified radius

 - parameter corners: Corners to round
 - parameter radius:  Radius to round to
 */
- (void)roundCorners:(UIRectCorner)corners radius:(CGFloat)radius;

/**
 Rounds the given set of corners to the specified radius with a border

 - parameter corners:     Corners to round
 - parameter radius:      Radius to round to
 - parameter borderColor: The border color
 - parameter borderWidth: The border width
 */
- (void)roundCorners:(UIRectCorner)corners radius:(CGFloat)radius borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth;

/**
 Fully rounds an autolayout view (e.g. one with no known frame) with the given diameter and border

 - parameter diameter:    The view's diameter
 - parameter borderColor: The border color
 - parameter borderWidth: The border width
 */
- (void)fullyRoundWithDiameter:(CGFloat)diameter borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth;

@end

UIView+RoundCorners.m

#import "UIView+RoundCorners.h"

@implementation UIView (RoundCorners)

- (void)roundCorners:(UIRectCorner)corners radius:(CGFloat)radius {
    [self _roundCorners:corners radius:radius];
}

- (void)roundCorners:(UIRectCorner)corners radius:(CGFloat)radius borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth {
    CAShapeLayer *mask = [self _roundCorners:corners radius:radius];
    [self addBorderWithMask:mask borderColor:borderColor borderWidth:borderWidth];
}

- (void)fullyRoundWithDiameter:(CGFloat)diameter borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth {
    self.layer.masksToBounds = YES;
    self.layer.cornerRadius = diameter / 2;
    self.layer.borderWidth = borderWidth;
    self.layer.borderColor = borderColor.CGColor;
}

- (CAShapeLayer *)_roundCorners:(UIRectCorner)corners radius:(CGFloat)radius {
    UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:self.bounds byRoundingCorners:corners cornerRadii:CGSizeMake(radius, radius)];
    CAShapeLayer *mask = [CAShapeLayer layer];
    mask.path = path.CGPath;
    self.layer.mask = mask;
    return mask;
}

- (void)addBorderWithMask:(CAShapeLayer *)mask borderColor:(UIColor *)borderColor borderWidth:(CGFloat)borderWidth {
    CAShapeLayer *borderLayer = [CAShapeLayer layer];
    borderLayer.path = mask.path;
    borderLayer.fillColor = UIColor.clearColor.CGColor;
    borderLayer.strokeColor = borderColor.CGColor;
    borderLayer.lineWidth = borderWidth;
    borderLayer.frame = self.bounds;
    [self.layer addSublayer:borderLayer];
}

@end
0
votes

One simple hack could be as following. Take views like below example in image. Red View will have rounded corners and Yellow View (inside Red View) will prevent the corners to be rounded

enter image description here

Now write below code for Red View.

        self.myView.layer.cornerRadius = 15

Make sure you don't write any code as clipsToBounds = true or masksToBounds = true.

Below image is the result

enter image description here

Placement of Yellow View will decide, which 2 corners will not be rounded. Hope this is easy to implement.