I make a label in Interface Builder, with constraints for fixed height and fixed width:
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:
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
Mystery solved.
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()
}
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.