Create a rectangle with just two rounded corners in swift?

66,105

Solution 1

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];

Solution 2

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

Solution 3

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.

Solution 4

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)

Solution 5

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)
Share:
66,105
Maralc
Author by

Maralc

Updated on July 30, 2022

Comments

  • Maralc
    Maralc almost 2 years

    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.

  • Maralc
    Maralc about 9 years
    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/Referenc‌​e/…
  • Sanjay Mohnani
    Sanjay Mohnani about 9 years
    are you asking about using start and end angles?
  • Sanjay Mohnani
    Sanjay Mohnani about 9 years
    #define RADIANS(degrees) ((degrees) / (180.0 / M_PI))
  • Sanjay Mohnani
    Sanjay Mohnani about 9 years
    and use as - double startAngle = RADIANS(45); double endAngle = RADIANS(135);
  • Sanjay Mohnani
    Sanjay Mohnani about 9 years
    perfect, it's definitely usable in swift as #define in objective-c
  • Andy
    Andy over 8 years
    how do you do this in swift 2.0? the "|" doesn't appear to work.
  • Onichan
    Onichan about 8 years
    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).
  • Onichan
    Onichan about 8 years
    Update: just had to call the extension in viewDidLayoutSubviews
  • iwasrobbed
    iwasrobbed about 8 years
    @Onichan I added a fullyRound method that works for autolayout views as well since the frame isn't setup if called from viewDidLoad code
  • iwasrobbed
    iwasrobbed almost 8 years
    @DaveG Works with any UIView subclass (such as UIButton, UILabel, UITextField, etc)
  • JSBach
    JSBach over 7 years
    sth wrong with the bottom using as [.bottomLeft, .topLeft] for radius
  • iwasrobbed
    iwasrobbed over 7 years
    @Dincer Looks normal to me. Playground: gist.github.com/iwasrobbed/6ebd47d64b921ca27809adaafb4cce83 / imgur.com/xZCZmO4 ... Could you be more specific please?
  • Fattie
    Fattie over 7 years
    (good one - as usual there have been many small changes in Swift, eg capitalization of constants, etc)
  • lifewithelliott
    lifewithelliott over 7 years
    @iwasrobbed 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!
  • satoukum
    satoukum about 7 years
    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.
  • Fattie
    Fattie about 7 years
    hi @satoukum - the correct way to handle that in modern Xcode is shown in my answer below, cheers
  • user1673099
    user1673099 about 7 years
    not working for bottom. i.e. bottomLeft & bottomRight
  • user1673099
    user1673099 about 7 years
    not working for bottom. i.e. bottomLeft & bottomRight
  • Sharukh Mastan
    Sharukh Mastan almost 7 years
    I had to set self.layer.masksToBounds = true in the view before invoking round() method to show the changes.
  • ricardopereira
    ricardopereira over 6 years
    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!
  • ricardopereira
    ricardopereira over 6 years
    Have in mind that the lineWidth doesn't check the screen scale, so the borderWidth should be multiplied with UIScreen.main.scale.
  • Bibek
    Bibek over 6 years
    How to use this when my view size is dynamic? lets say multiline label. When should I call the function to round?
  • iwasrobbed
    iwasrobbed over 6 years
    @Dari See above: in the viewDidLayoutSubviews lifecycle callback
  • Piotr Wasilewicz
    Piotr Wasilewicz about 6 years
    not working in Swift 4. Using [.bottomLeft, .bottomRight] I have rounded only bottomLeft corner.
  • Piotr Wasilewicz
    Piotr Wasilewicz about 6 years
    what If I want only three of four corners? [.bottomLeft, .bottomRight, .topRight] not working.
  • monolith
    monolith almost 6 years
    big caveat, this solution is only iOS 11+
  • Vadlapalli Masthan
    Vadlapalli Masthan almost 5 years
    how can set border or shadow to this view ?
  • budiDino
    budiDino almost 5 years
    @VadlapalliMasthan the same way you usually would. Just make sure the frame of the view is set before rounding corners and applying shadow and border
  • Rey Bruno
    Rey Bruno almost 5 years
    Work for me, topLeft and topRight corners, swift 4.0
  • Tiago Martins Peres
    Tiago Martins Peres over 4 years
    Hi Mohammad Akbari, welcome. Please consider adding an explanation and format the code properly.
  • Amit Thakur
    Amit Thakur about 4 years
    For top two corners you can use view.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
  • brainray
    brainray about 4 years
    Great one thanks. While the naming like layerMinXMinYCorner is technically correct I wonder why Swift has to be that ugly at times
  • Pavan Kumar
    Pavan Kumar almost 4 years
    @iwasrobbed Only works for topLeft and bottomLeft. Not working for right side, i.e., topRight, bottomRight. Any suggestion.