20

I have the following code in a category that does rounding of corners. I would also like to draw a border. But the border is not shown on the rounded part of the corner.

enter image description here

Here is the code

- (void) roundTopCorners:(CGFloat) radius
{
    self.layer.masksToBounds = YES;

    CGRect bounds = self.bounds;
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:bounds byRoundingCorners:(UIRectCornerTopLeft | UIRectCornerTopRight) cornerRadii:CGSizeMake(radius, radius)];

    CAShapeLayer *maskLayer = [CAShapeLayer layer];
    maskLayer.frame = bounds;
    maskLayer.path = maskPath.CGPath;
    maskLayer.strokeColor = [UIColor redColor].CGColor;

    self.layer.mask = maskLayer;
}
aniruddhc
  • 320
  • 1
  • 3
  • 15
  • "Note that if you're trying to use this style of framing on views that dynamically resize (in response to autorotation for instance) you'll have to be a little more careful, since the layers can't take advantage of auto layout. In that case your best solution is probably to create a custom UITextField and implement layerClass to return a custom layer class that dynamically resizes layers similar to those created above. @David Berry" actually its not that ticky, if you wanna resize all these layers, just put all the code in the viewDidAppear, it will work! – Ernie Sender Jul 06 '17 at 06:34

4 Answers4

39

The mask layer doesn't get drawn, just used to compute the mask. Try:

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

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

    self.layer.mask = maskLayer;

    CAShapeLayer*   frameLayer = [CAShapeLayer layer];
    frameLayer.frame = bounds;
    frameLayer.path = maskPath.CGPath;
    frameLayer.strokeColor = [UIColor redColor].CGColor;
    frameLayer.fillColor = nil;

    [self.layer addSublayer:frameLayer];
}

-(void)roundTopCornersRadius:(CGFloat)radius
{
    [self roundCorners:(UIRectCornerTopLeft|UIRectCornerTopRight) radius:radius];
}

-(void)roundBottomCornersRadius:(CGFloat)radius
{
    [self roundCorners:(UIRectCornerBottomLeft|UIRectCornerBottomRight) radius:radius];
}

The frame you're currently seeing drawn is the UITextField's normal frame, so set the frame style to none. You'll also have to adjust the insets to make up for the fact that with the frame style set to none there's normally no inset.

David Berry
  • 40,941
  • 12
  • 84
  • 95
  • Note that if you're trying to use this style of framing on views that dynamically resize (in response to autorotation for instance) you'll have to be a little more careful, since the layers can't take advantage of auto layout. In that case your best solution is probably to create a custom UITextField and implement layerClass to return a custom layer class that dynamically resizes layers similar to those created above. – David Berry Mar 14 '14 at 20:38
  • 3
    This is a good approach. However, the border will not be the right thickness. This is because the stroke for the border is half within and half outside the mask. It is easy enough to fix - just add the following: `frameLayer.lineWidth = borderWidth*2;` where borderWidth is your desired border width. – BooTooMany Mar 19 '14 at 17:57
  • @BooTooMany at some point something similar was in there to account for the line width. I think I was actually insetting the path by the appropriate amount, but doubling the thickness works about the same and is simpler. – David Berry Mar 19 '14 at 19:16
  • How to call these methods in uitableviewcell – reza_khalafi Jun 11 '17 at 07:21
13

Swift version of David Berry's answer:

func roundCorners(corners:UIRectCorner, radius:CGFloat) {
    let bounds = self.bounds

    let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSizeMake(radius, radius))

    let maskLayer = CAShapeLayer()
    maskLayer.frame = bounds
    maskLayer.path = maskPath.CGPath

    self.layer.mask = maskLayer

    let frameLayer = CAShapeLayer()
    frameLayer.frame = bounds
    frameLayer.path = maskPath.CGPath
    frameLayer.strokeColor = UIColor.redColor().CGColor
    frameLayer.fillColor = nil

    self.layer.addSublayer(frameLayer)
}

func roundTopCornersRadius(radius:CGFloat) {
    self.roundCorners([UIRectCorner.TopLeft, UIRectCorner.TopRight], radius:radius)
}

func roundBottomCornersRadius(radius:CGFloat) {
    self.roundCorners([UIRectCorner.BottomLeft, UIRectCorner.BottomRight], radius:radius)
}
Vojtech Vrbka
  • 5,342
  • 6
  • 44
  • 63
4

this is probably a very late answer but I would just like to share the solution I came up with based on answers of different people from different similar questions. I got big help from Vojtech's answer above.

extension UIView {
    func EZRoundCorners(corners:UIRectCorner, radius: CGFloat) -> CAShapeLayer {
        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
        return mask
    }

    func EZRoundCornersWithBorder(corners:UIRectCorner, radius:CGFloat, color:UIColor, width:CGFloat) -> CAShapeLayer {

        let mask = self.EZRoundCorners(corners, radius: radius)

        // Add border
        let borderLayer = EZCALayer()
        borderLayer.path = mask.path // Reuse the Bezier path
        borderLayer.fillColor = UIColor.clearColor().CGColor
        borderLayer.strokeColor = color.CGColor
        borderLayer.lineWidth = width
        borderLayer.frame = self.bounds
        self.layer.addSublayer(borderLayer)
        return borderLayer
    }

    func removeEZLayers () {
        for layer in self.layer.sublayers! {
            if layer is EZCALayer {
                layer.removeFromSuperlayer()
            }
        }
    }
}

class EZCALayer : CAShapeLayer {
}

I inherited from CAShapeLayer so I can remove the border sublayers if I don't want to use them anymore.

cessmestreet
  • 2,298
  • 3
  • 22
  • 42
1
      label.layer.cornerRadius = 3.0 
     label.layer.maskedCorners = [.layerMinXMinYCorner,.layerMinXMaxYCorner]//round top left and bottom left corners

Source:https://www.hackingwithswift.com/example-code/calayer/how-to-round-only-specific-corners-using-maskedcorners

Image of working solution

Diviyo
  • 47
  • 1
  • 3