Create a rectangle with just two rounded corners in swift?
Asked Answered
M

16

55

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.

Mildew answered 14/4, 2015 at 3:40 Comment(3)
developer.apple.com/library/ios/documentation/UIKit/Reference/…: bezierPathWithRoundedRect:byRoundingCorners:cornerRadii:Grange
As usual there have been *many small changes in Swift, eg capitalization of constants, etc etc. Suggest scroll down to newest answer.Lamarckian
Check out my answer here, it'll cover everything:- https://mcmap.net/q/23727/-how-to-set-cornerradius-for-only-top-left-and-top-right-corner-of-a-uiviewAlvaalvan
R
58

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];
Rehash answered 14/4, 2015 at 4:21 Comment(9)
I could solve my problem with your answer. But just curious as I was trying to use the UIBezierPath.addArcWithCenter to draw the rounded corners myself and the start and end angle doesn't match at all the ones documented on developer.apple.com/library/ios/documentation/UIKit/Reference/…Mildew
are you asking about using start and end angles?Rehash
#define RADIANS(degrees) ((degrees) / (180.0 / M_PI))Rehash
and use as - double startAngle = RADIANS(45); double endAngle = RADIANS(135);Rehash
perfect, it's definitely usable in swift as #define in objective-cRehash
how do you do this in swift 2.0? the "|" doesn't appear to work.Correia
not working for bottom. i.e. bottomLeft & bottomRightDecipher
not working in Swift 4. Using [.bottomLeft, .bottomRight] I have rounded only bottomLeft corner.Secondguess
Work for me, topLeft and topRight corners, swift 4.0Khoisan
N
70

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)
    }
    
}
Normative answered 25/2, 2016 at 8:14 Comment(13)
I'm using this on a UITextField but it alters the width of the text field. Why does it change the width (which was set using Autolayout).Fulguration
Update: just had to call the extension in viewDidLayoutSubviewsFulguration
@Fulguration I added a fullyRound method that works for autolayout views as well since the frame isn't setup if called from viewDidLoad codeNormative
@DaveG Works with any UIView subclass (such as UIButton, UILabel, UITextField, etc)Normative
sth wrong with the bottom using as [.bottomLeft, .topLeft] for radiusMemorable
@Dincer Looks normal to me. Playground: gist.github.com/iwasrobbed/6ebd47d64b921ca27809adaafb4cce83 / imgur.com/xZCZmO4 ... Could you be more specific please?Normative
@Normative I am calling the round method on a button inside override func viewDidLayoutSubviews() and it is introducing a lot of lag as well as adjusted the position/shrank the button. Also have attempted : @IBOutlet fileprivate weak var button: UIButton! { didSet { button.round(corners: [.topRight, .bottomLeft], radius: 50 , borderColor: UIColor.blue, borderWidth: 5.0) button.layoutSubviews() } } the lag is now gone but the button is still shrunk/not constrained properly. Thanks! Thanks!Sanalda
I had to set self.layer.masksToBounds = true in the view before invoking round() method to show the changes.Numerous
Be careful with the addBorder(mask:borderColor:borderWidth:) method because it's always adding a new layer. If viewDidLayoutSubviews or layoutSubviews is called 5 times using the round(corners:radius:borderColor:borderWidth:) method in an empty UIView... that view will have 5 sublayers!Sonnnie
Have in mind that the lineWidth doesn't check the screen scale, so the borderWidth should be multiplied with UIScreen.main.scale.Sonnnie
How to use this when my view size is dynamic? lets say multiline label. When should I call the function to round?Philippe
@Dari See above: in the viewDidLayoutSubviews lifecycle callbackNormative
@Normative Only works for topLeft and bottomLeft. Not working for right side, i.e., topRight, bottomRight. Any suggestion.Miquelmiquela
T
69

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.

Thermosiphon answered 17/5, 2018 at 16:35 Comment(3)
big caveat, this solution is only iOS 11+Pinniped
For top two corners you can use view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]Cuffs
Great one thanks. While the naming like layerMinXMinYCorner is technically correct I wonder why Swift has to be that ugly at timesIorgos
R
58

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];
Rehash answered 14/4, 2015 at 4:21 Comment(9)
I could solve my problem with your answer. But just curious as I was trying to use the UIBezierPath.addArcWithCenter to draw the rounded corners myself and the start and end angle doesn't match at all the ones documented on developer.apple.com/library/ios/documentation/UIKit/Reference/…Mildew
are you asking about using start and end angles?Rehash
#define RADIANS(degrees) ((degrees) / (180.0 / M_PI))Rehash
and use as - double startAngle = RADIANS(45); double endAngle = RADIANS(135);Rehash
perfect, it's definitely usable in swift as #define in objective-cRehash
how do you do this in swift 2.0? the "|" doesn't appear to work.Correia
not working for bottom. i.e. bottomLeft & bottomRightDecipher
not working in Swift 4. Using [.bottomLeft, .bottomRight] I have rounded only bottomLeft corner.Secondguess
Work for me, topLeft and topRight corners, swift 4.0Khoisan
A
26

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)
Abet answered 16/4, 2017 at 6:7 Comment(2)
how can set border or shadow to this view ?Achorn
@VadlapalliMasthan the same way you usually would. Just make sure the frame of the view is set before rounding corners and applying shadow and borderAbet
W
11

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)
Wun answered 6/10, 2016 at 2:21 Comment(3)
(good one - as usual there have been many small changes in Swift, eg capitalization of constants, etc)Lamarckian
For me, sometimes the image wouldn't show when I rounded the top corners, so I needed to add myView.layoutIfNeeded() to the line before.Sexist
hi @Sexist - the correct way to handle that in modern Xcode is shown in my answer below, cheersLamarckian
L
9

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))
Lamarckian answered 9/1, 2017 at 17:41 Comment(0)
L
9

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])
Linkous answered 16/1, 2019 at 16:55 Comment(0)
C
8

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))
Correia answered 19/9, 2015 at 16:11 Comment(1)
not working for bottom. i.e. bottomLeft & bottomRightDecipher
L
4

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
Legality answered 16/10, 2017 at 11:19 Comment(1)
what If I want only three of four corners? [.bottomLeft, .bottomRight, .topRight] not working.Secondguess
M
3

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)
    }

}
Melvinamelvyn answered 28/9, 2016 at 13:20 Comment(0)
A
2
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)
Afterdinner answered 2/2, 2020 at 12:41 Comment(1)
Hi Mohammad Akbari, welcome. Please consider adding an explanation and format the code properly.Hackworth
P
1

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
Playback answered 4/9, 2019 at 13:43 Comment(0)
G
1
view.layer.cornerRadius = 10.0
view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner, .layerMinXMaxYCorner]

Best way to do it!

Georgetta answered 15/9, 2020 at 14:56 Comment(0)
M
1

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

yourView.layer.cornerRadius = 12
yourView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
Misbehavior answered 27/1, 2021 at 17:34 Comment(0)
L
0

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
Lentiginous answered 1/6, 2016 at 19:46 Comment(0)
S
0

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.

Shoran answered 22/3, 2019 at 7:25 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.