Search code examples
iosbordercalayerclip

Round borders aren't clipping/masking perfectly


I make a label in Interface Builder, with constraints for fixed height and fixed width:
enter image description here

I subclass it to give it a white round border:

class CircularLabel: UILabel {
    override func awakeFromNib() {
        super.awakeFromNib()
        layer.cornerRadius = frame.size.height / 2
        layer.borderColor = UIColor.white.cgColor
        layer.borderWidth = 5
        layer.masksToBounds = true
        clipsToBounds = true
    }
}

But the clipping/masking isn't good at runtime:
enter image description here

I was expecting a perfect white border, without orange pixels.

iPhone 8 (Simulator and real device), iOS 11.2, Xcode 9.2, Swift 3.2

MCVE at https://github.com/Coeur/stackoverflow48658502


Solution

  • Mystery solved.

    Nice solution

    Add a 1 pixel stroke and masksToBounds will do the job for clipping the edges correctly:

    override func draw(_ rect: CGRect) {
        super.draw(rect)
        // workaround incomplete borders: https://stackoverflow.com/a/48663935/1033581
        UIColor(cgColor: layer.borderColor!).setStroke()
        let path = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius)
        path.lineWidth = 1
        path.stroke()
    }
    

    Explanations

    Actually, from my tests, setting layer.borderWidth = 5 is equivalent to formula:

    let borderWidth: CGFloat = 5
    UIColor(cgColor: layer.borderColor!).setStroke()
    let path = UIBezierPath(roundedRect: bounds.insetBy(dx: borderWidth / 2, dy: borderWidth / 2),
                            cornerRadius: layer.cornerRadius - borderWidth / 2)
    path.lineWidth = borderWidth
    path.stroke()
    

    But on the other hand layer.cornerRadius = frame.size.height / 2 + layer.masksToBounds = true is going to clip with a different unknown method that has a different aliasing formula on the edges. Because the clipping and the drawing don't have the same aliasing, there are some pixels displaying the background color instead of the border color.