44

I know I can round all four corners using:

 myBtn.layer.cornerRadius = 8
 myBtn.layer.masksToBounds = true

Since I only want to round two, I did some research and found this:

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

Which is called like this:

view.roundCorners([.TopLeft , .TopRight], radius: 10)

Yet this doesn't work for a UIButton. When I switch the extension to be for type UIButton and pass it a button , the output looks like this:

enter image description here

The question is, how do I adapt this to work on a UIButton?

Community
  • 1
  • 1
Dave G
  • 12,042
  • 7
  • 57
  • 83
  • Please consider accepting my answer, because it is the most precise answer to the problem. Future visitors of this question will not be satisfied just by seeing a bunch of code that works, but does not explain why it works (that's what the currently accepted answer does) but rather by seeing _where_ the problem is – and that is nailed down by my answer. – return true May 13 '16 at 12:42
  • see my answer here: http://stackoverflow.com/a/40222533/2594699 – Nhat Dinh Oct 24 '16 at 15:51

10 Answers10

65

Swift 4: For latest iOS 11 onwards

override func viewDidLoad() {
    super.viewDidLoad()

    if #available(iOS 11.0, *) {
        self.viewToRound.clipsToBounds = true
        viewToRound.layer.cornerRadius = 20
        viewToRound.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMinYCorner]
    } else {
        // Fallback on earlier versions
    }
}

Earlier iOS (10,9 etc) Versions (works for iOS 11 too)

override func viewDidLayoutSubviews() {
    self.viewToRound.clipsToBounds = true
    let path = UIBezierPath(roundedRect: viewToRound.bounds,
                            byRoundingCorners: [.topRight, .topLeft],
                            cornerRadii: CGSize(width: 20, height: 20))

    let maskLayer = CAShapeLayer()

    maskLayer.path = path.cgPath
    viewToRound.layer.mask = maskLayer
}
Naishta
  • 11,885
  • 4
  • 72
  • 54
59

Adding Extension of UIButton:

extension UIButton{
    func roundedButton(){
        let maskPath1 = UIBezierPath(roundedRect: bounds,
            byRoundingCorners: [.topLeft , .topRight],
            cornerRadii: CGSize(width: 8, height: 8))
        let maskLayer1 = CAShapeLayer()
        maskLayer1.frame = bounds
        maskLayer1.path = maskPath1.cgPath
        layer.mask = maskLayer1
    }
}

Calling in viewDidAppear/viewDidLayoutSubviews:

btnCorner.roundedButton()

Button Corner OutPut:

enter image description here

Kirit Modi
  • 23,155
  • 15
  • 89
  • 112
  • Do you know how I can remove this from the button after? If I want to reuse the button but without rounding? NVM, here it is: myBtn.layer.mask?.removeFromSuperlayer() – Dave G May 11 '16 at 14:26
  • 3
    Doesn't work for when buttons are in stack view. When rounding corner bottom left and right corners, it rounds only bottom left corner. When using buttons that are not embedded in a stack view, it works totally fine – Lucas Sep 02 '18 at 16:14
  • 1
    Using the frame like this will not work when resizing the button. e.g. adding the button to a UIStackView. – Womble Jul 25 '19 at 05:37
  • How can I round all 4 corners with this extension? – Ximena Flores de la Tijera Sep 25 '21 at 00:40
20

For swift 3 Kirit Modi's answer is changed to:

extension UIButton {
   func roundedButton(){
       let maskPAth1 = UIBezierPath(roundedRect: self.bounds,
                                    byRoundingCorners: [.topLeft , .topRight],
                                    cornerRadii:CGSize(width:8.0, height:8.0))
       let maskLayer1 = CAShapeLayer()
       maskLayer1.frame = self.bounds
       maskLayer1.path = maskPAth1.cgPath
       self.layer.mask = maskLayer1
   }
}

At the start of the extension's file don't forget to add:

import UIKit

If you want an extension for a UIView with the option of rounding top or bottom corners you can use:

extension UIView {
   func roundedCorners(top: Bool){
       let corners:UIRectCorner = (top ? [.topLeft , .topRight] : [.bottomRight , .bottomLeft])        
       let maskPAth1 = UIBezierPath(roundedRect: self.bounds,
                                    byRoundingCorners: corners,
                                    cornerRadii:CGSize(width:8.0, height:8.0))
       let maskLayer1 = CAShapeLayer()
       maskLayer1.frame = self.bounds
       maskLayer1.path = maskPAth1.cgPath
       self.layer.mask = maskLayer1
   }
}

Which is called for a button as:

myButton.roundedCorners(top: true)
C. Kontos
  • 1,198
  • 12
  • 21
12

iOS 11 has made it really easy to round corners. The code below rounds the top left and bottom right corners.

myView.layer.maskedCorners = [.layerMinXMinYCorner, .layerMaxXMaxYCorner]
dsunku
  • 503
  • 5
  • 9
  • Thanks .. /* Defines which of the four corners receives the masking when using * `cornerRadius' property. Defaults to all four corners. */ .button.layer.cornerRadius = 8 button.layer.masksToBounds = true button.clipsToBounds = true button.layer.maskedCorners = [.layerMaxXMaxYCorner] – Hardik Darji Mar 14 '18 at 10:24
9

For swift 5 and the most flexibility

Define an extension with a roundCorners function

extension UIButton {
    func roundCorners(corners: UIRectCorner, radius: Int = 8) {
        let maskPath1 = UIBezierPath(roundedRect: bounds,
                                     byRoundingCorners: corners,
                                     cornerRadii: CGSize(width: radius, height: radius))
        let maskLayer1 = CAShapeLayer()
        maskLayer1.frame = bounds
        maskLayer1.path = maskPath1.cgPath
        layer.mask = maskLayer1
    }
}

Call the roundCorners function

myButton.roundCorners(corners: [.topLeft, .topRight])

enter image description here

Or with a specific radius

myButton.roundCorners(corners: [.topLeft, .topRight], radius: 20)

enter image description here

Scott
  • 1,322
  • 4
  • 23
  • 37
6

Update you extension to be like this:

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

The shape layer (mask) needs to know the frame

Edan
  • 167
  • 14
Hossam Ghareeb
  • 7,063
  • 3
  • 53
  • 64
  • 1
    thanks for the code, it is quite usefull in many scenarios. but please change "maskLayer.frame = rect" to "mask.frame = rect" – dreampowder Nov 28 '17 at 08:05
2

Use this Code,

let path = UIBezierPath(roundedRect:viewTo.bounds, byRoundingCorners:[.TopRight, .TopLeft], cornerRadii: CGSizeMake(20, 20))
let maskLayer = CAShapeLayer()
maskLayer.path = path.CGPath
viewTo.layer.mask = maskLayer

hope its helpful

Iyyappan Ravi
  • 3,205
  • 2
  • 16
  • 30
2

That's what helped me

extension UIView {

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

      let mask = CAShapeLayer()
      mask.path = path.cgPath
      layer.mask = mask
   }
}

Pay attention to the fact that if you have layout constraints attached to it, you must refresh this as follows in your UIView subclass:

override func viewDidLayoutSubviews() {
   super.viewDidLayoutSubviews()

   yourButtonOutletName.roundCorners(
      corners: [.topLeft, .topRight],
      radius: yourButtonOutletName.frame.height / 2
   )
}
Ars
  • 21
  • 2
0

You forgot to set the frame of your shape layer:

mask.frame = layer.bounds
return true
  • 7,839
  • 2
  • 19
  • 35
0

rounding is applied to corner's of view/Button .. , but coming to border of button , it is not applying correctly. can I have any solution for that? @

Here is the code that I have used , which is working (border) in iOS11.0 and above and not in below versions(<11.0)

if #available(iOS 11.0, *) {
        self.layer.cornerRadius = radius
        self.layer.maskedCorners = maskedCorners
    } else {
        let shapeLayer = CAShapeLayer()
        shapeLayer.position = self.center
        self.layer.masksToBounds = true
        self.clipsToBounds = true
        let bezirePath = UIBezierPath(roundedRect: self.bounds,
                                      byRoundingCorners: corners,
                                      cornerRadii: CGSize(width: radius, height: radius))
        shapeLayer.bounds = frame
        shapeLayer.path = bezirePath.cgPath

        self.layer.mask = shapeLayer