cornerRadius with border: Glitch around border

2020-02-26 08:57发布

My application is mostly round- and borderbased.

I use the layer property of UIView to give a corner radius and a border.

But I am facing a problem that the corners are not clear.

I am getting the following results:

UIButton

screenshot of a button with rounded corners and a border

UIImageView

screenshot of an image view with rounded corners and a border

You can observe a thin border line around the white or grey border.

This is my code:

button.layer.borderWidth = 2.0;
button.layer.borderColor = [[UIColor whiteColor] CGColor];
button.layer.cornerRadius = 4;

button.clipsToBounds = YES;

I have searched to solve this but I'm not getting success.

I have tried button.layer.masksToBounds = YES, but with no effect.

Am I missing anything? Or are there any other methods which can give me better results compared to CALayer?

6条回答
够拽才男人
2楼-- · 2020-02-26 09:20

The answer by CRDave works great but has one flaw: When called multiple times like on size change, it keeps adding layers. Instead previous layers should be updated.
See below for an updated ObjC version. For swift please adapt accordingly.

//  UIView+Border.h
#import <UIKit/UIKit.h>

@interface UIView (Border)
- (void)setBorderWithCornerRadius:(CGFloat)radius
                            color:(UIColor *)borderColor
                            width:(CGFloat)borderWidth;
@end

//  UIView+Border.m
#import "UIView+Border.h"

@implementation UIView (Border)
- (void)setBorderWithCornerRadius:(CGFloat)radius
                            color:(UIColor *)borderColor
                            width:(CGFloat)borderWidth {
    CGRect rect = self.bounds;

    //Make round
    // Create the path for to make circle
    UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect
                                                   byRoundingCorners:UIRectCornerAllCorners
                                                         cornerRadii:CGSizeMake(radius, radius)];

    // Create the shape layer and set its path
    CAShapeLayer *maskLayer = [CAShapeLayer layer];

    maskLayer.frame = rect;
    maskLayer.path  = maskPath.CGPath;

    // Set the newly created shape layer as the mask for the view's layer
    self.layer.mask = maskLayer;

    //Give Border
    //Create path for border
    UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:rect
                                                     byRoundingCorners:UIRectCornerAllCorners
                                                           cornerRadii:CGSizeMake(radius, radius)];

    // Create the shape layer and set its path
    NSString *layerName = @"ig_border_layer";
    CAShapeLayer *borderLayer = (CAShapeLayer *)[[[self.layer sublayers] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:@"name == %@", layerName]] firstObject];
    if (!borderLayer) {
        borderLayer = [CAShapeLayer layer];
        [borderLayer setName:layerName];
        //Add this layer to give border.
        [[self layer] addSublayer:borderLayer];
    }

    borderLayer.frame       = rect;
    borderLayer.path        = borderPath.CGPath;
    borderLayer.strokeColor = [UIColor whiteColor].CGColor;
    borderLayer.fillColor   = [UIColor clearColor].CGColor;
    borderLayer.lineWidth   = borderWidth;
}
@end
查看更多
再贱就再见
3楼-- · 2020-02-26 09:22

delete

button.layer.borderWidth = 0.3;
button.layer.borderColor = [[UIColor blueMain] CGColor];
查看更多
我欲成王,谁敢阻挡
4楼-- · 2020-02-26 09:29

Here is a Swift 5 version of @CRDave's answer as an extension of UIView:

protocol CornerRadius {
    func makeBorderWithCornerRadius(radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat)
}

extension UIView: CornerRadius {

    func makeBorderWithCornerRadius(radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
        let rect = self.bounds

        let maskPath = UIBezierPath(roundedRect: rect,
                                    byRoundingCorners: .allCorners,
                                    cornerRadii: CGSize(width: radius, height: radius))

        // Create the shape layer and set its path
        let maskLayer = CAShapeLayer()
        maskLayer.frame = rect
        maskLayer.path  = maskPath.cgPath

        // Set the newly created shape layer as the mask for the view's layer
        self.layer.mask = maskLayer

        // Create path for border
        let borderPath = UIBezierPath(roundedRect: rect,
                                      byRoundingCorners: .allCorners,
                                      cornerRadii: CGSize(width: radius, height: radius))

        // Create the shape layer and set its path
        let borderLayer = CAShapeLayer()

        borderLayer.frame       = rect
        borderLayer.path        = borderPath.cgPath
        borderLayer.strokeColor = borderColor.cgColor
        borderLayer.fillColor   = UIColor.clear.cgColor
        borderLayer.lineWidth   = borderWidth * UIScreen.main.scale

        //Add this layer to give border.
        self.layer.addSublayer(borderLayer)
    }

}
查看更多
男人必须洒脱
5楼-- · 2020-02-26 09:40

de.'s answer worked much better for me than CRDave's answer.

I had to translate it from Swift, so I thought I'd go ahead and post the translation:

extension UIView {
    func giveBorderWithCornerRadius(cornerRadius r: CGFloat, borderColor c: UIColor, strokeWidth w: CGFloat) {
        let rect = self.bounds

        let maskPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: r, height: r))

        let maskLayer = CAShapeLayer()

        maskLayer.frame = rect
        maskLayer.path = maskPath.cgPath

        self.layer.mask = maskLayer

        let borderPath = UIBezierPath(roundedRect: rect, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: r, height: r))

        let layerName = "border_layer"
        var borderLayer: CAShapeLayer? = self.layer.sublayers?.filter({ (c) -> Bool in
            if c.name == layerName {
                return true
            } else {
                return false
            }
        }).first as? CAShapeLayer

        if borderLayer == nil {
            borderLayer = CAShapeLayer()
            borderLayer!.name = layerName
            self.layer.addSublayer(borderLayer!)
        }

        borderLayer!.frame = rect
        borderLayer!.path = borderPath.cgPath
        borderLayer!.strokeColor = c.cgColor
        borderLayer!.fillColor = UIColor.clear.cgColor
        borderLayer!.lineWidth = w
    }
}

I'm calling the method from layoutSubviews()

查看更多
放荡不羁爱自由
6楼-- · 2020-02-26 09:43

I tried many solution and end by using UIBezierPath.

I create category of UIView and add method to make round rect and border.

This is method of that category:

- (void)giveBorderWithCornerRadious:(CGFloat)radius borderColor:(UIColor *)borderColor andBorderWidth:(CGFloat)borderWidth
{
    CGRect rect = self.bounds;

    //Make round
        // Create the path for to make circle
        UIBezierPath *maskPath = [UIBezierPath bezierPathWithRoundedRect:rect
                                                       byRoundingCorners:UIRectCornerAllCorners
                                                             cornerRadii:CGSizeMake(radius, radius)];

        // Create the shape layer and set its path
        CAShapeLayer *maskLayer = [CAShapeLayer layer];

        maskLayer.frame = rect;
        maskLayer.path  = maskPath.CGPath;

        // Set the newly created shape layer as the mask for the view's layer
        self.layer.mask = maskLayer;

    //Give Border
        //Create path for border
        UIBezierPath *borderPath = [UIBezierPath bezierPathWithRoundedRect:rect
                                                         byRoundingCorners:UIRectCornerAllCorners
                                                               cornerRadii:CGSizeMake(radius, radius)];

        // Create the shape layer and set its path
        CAShapeLayer *borderLayer = [CAShapeLayer layer];

        borderLayer.frame       = rect;
        borderLayer.path        = borderPath.CGPath;
        borderLayer.strokeColor = [UIColor whiteColor].CGColor;
        borderLayer.fillColor   = [UIColor clearColor].CGColor;
        borderLayer.lineWidth   = borderWidth;

        //Add this layer to give border.
        [[self layer] addSublayer:borderLayer];
}

I get idea of using UIBezierPath from this amazing article: Thinking like a Bézier path

I get most of code from this two link:

Note: This is category method so self represent view on which this method is called. Like UIButton, UIImageView etc.

查看更多
放荡不羁爱自由
7楼-- · 2020-02-26 09:45

This is Kamil Nomtek.com's answer updated to Swift 3+ and with some refinements (mostly semantics/naming and using a class protocol).

protocol RoundedBorderProtocol: class {
    func makeBorder(with radius: CGFloat, borderWidth: CGFloat, borderColor: UIColor)
}

extension UIView: RoundedBorderProtocol {
    func makeBorder(with radius: CGFloat, borderWidth: CGFloat, borderColor: UIColor) {
        let maskPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: radius, height: radius))

        // Create the shape layer and set its path
        let maskLayer = CAShapeLayer()
        maskLayer.frame = bounds
        maskLayer.path = maskPath.cgPath

        // Set the newly created shape layer as the mask for the view's layer
        layer.mask = maskLayer

        //Create path for border
        let borderPath = UIBezierPath(roundedRect: bounds, byRoundingCorners: .allCorners, cornerRadii: CGSize(width: radius, height: radius))

        // Create the shape layer and set its path
        let borderLayer = CAShapeLayer()

        borderLayer.frame = bounds
        borderLayer.path = borderPath.cgPath
        borderLayer.strokeColor = borderColor.cgColor
        borderLayer.fillColor = UIColor.clear.cgColor

        //The border is in the center of the path, so only the inside is visible.
        //Since only half of the line is visible, we need to multiply our width by 2.
        borderLayer.lineWidth = borderWidth * 2

        //Add this layer to display the border
        layer.addSublayer(borderLayer)
    }
}
查看更多
登录 后发表回答