圆形边框不 clipping/masking 完美
Round borders aren't clipping/masking perfectly
我在 Interface Builder 中制作了一个标签,具有固定高度和固定宽度的约束:
我将其子类化为白色圆形边框:
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
}
}
但是 clipping/masking 在运行时并不好:
我期待完美的白色边框,没有橙色像素。
iPhone8(模拟器和真机),iOS11.2,Xcode9.2,Swift3.2
您应该使用 UIBezierPath 来圆角并使用相同的路径绘制边界线。
我的情况是我创建了 CAShapeLayer 并进行了所有调整并将其添加为要查看的子层。
let borderLayer = CAShapeLayer()
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: *get your view bounds*, cornerRadius: *needed radius*).cgPath
//Set this rounding path to both layers
shapeLayer.path = path
borderLayer.path = path
//adjust border layer
borderLayer.lineWidth = *border width*
borderLayer.strokeColor = *cgColor of your border*
//apply shape layer as mask to your view, it will cut your view by the corners
*yourViewInstance*.layer.mask = shapeLayer
//Set fill color for border layer as clear, because technically it just puts colored layer over your view
borderLayer.fillColor = UIColor.clear.cgColor
//Add border layer as sublayer to your view's main layer
*your view instance*.layer.addSublayer(borderLayer)
在您的情况下,动态标签的文本可能存在问题:如果文本是例如900000 它将绘制在边框下。要解决这个问题,您可以将 UILAbel 放在另一个视图(将包含形状和边框调整)中并对其进行布局。
示例:
Structure and constraints
What i got: container BG - orange, border - white, superview's BG - red
Controller 的 viewDidLoad 方法代码:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.red
self.containerView.backgroundColor = UIColor.orange
self.label.backgroundColor = UIColor.clear
self.label.textAlignment = .center
self.label.adjustsFontSizeToFitWidth = true
self.label.text = "9000000"
//Create Border and shape and apply it to container view
let borderLayer = CAShapeLayer()
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: containerView.bounds, cornerRadius: containerView.bounds.width / 2).cgPath
//Set this rounding path to both layers
shapeLayer.path = path
borderLayer.path = path
//adjust border layer
borderLayer.lineWidth = 20
borderLayer.strokeColor = UIColor.white.cgColor
//apply shape layer as mask to your view, it will cut your view by the corners
self.containerView.layer.mask = shapeLayer
//Set fill color for border layer as clear, because technically it just puts colored layer over your view
borderLayer.fillColor = UIColor.clear.cgColor
//Add border layer as sublayer to your view's main layer
self.containerView.layer.addSublayer(borderLayer)
}
另一个解决方案是完全忘记不完美的 borderWidth
并在彼此内部使用 两个视图:
extension UIView {
func roundBounds() {
layer.cornerRadius = frame.size.height / 2
clipsToBounds = true
}
}
class RoundLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
roundBounds()
}
}
class RoundView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
roundBounds()
}
}
谜底已解。
很好的解决方案
添加一个 1 像素的描边,masksToBounds
将完成正确剪裁边缘的工作:
override func draw(_ rect: CGRect) {
super.draw(rect)
// workaround incomplete borders:
UIColor(cgColor: layer.borderColor!).setStroke()
let path = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius)
path.lineWidth = 1
path.stroke()
}
解释
实际上,根据我的测试,设置 layer.borderWidth = 5
等同于公式:
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()
但另一方面,layer.cornerRadius = frame.size.height / 2
+ layer.masksToBounds = true
将使用不同的未知方法进行裁剪,该方法在边缘具有不同的别名公式。因为裁剪和绘图没有相同的别名,所以有一些像素显示背景颜色而不是边框颜色。
我在 Interface Builder 中制作了一个标签,具有固定高度和固定宽度的约束:
我将其子类化为白色圆形边框:
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
}
}
但是 clipping/masking 在运行时并不好:
我期待完美的白色边框,没有橙色像素。
iPhone8(模拟器和真机),iOS11.2,Xcode9.2,Swift3.2
您应该使用 UIBezierPath 来圆角并使用相同的路径绘制边界线。 我的情况是我创建了 CAShapeLayer 并进行了所有调整并将其添加为要查看的子层。
let borderLayer = CAShapeLayer()
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: *get your view bounds*, cornerRadius: *needed radius*).cgPath
//Set this rounding path to both layers
shapeLayer.path = path
borderLayer.path = path
//adjust border layer
borderLayer.lineWidth = *border width*
borderLayer.strokeColor = *cgColor of your border*
//apply shape layer as mask to your view, it will cut your view by the corners
*yourViewInstance*.layer.mask = shapeLayer
//Set fill color for border layer as clear, because technically it just puts colored layer over your view
borderLayer.fillColor = UIColor.clear.cgColor
//Add border layer as sublayer to your view's main layer
*your view instance*.layer.addSublayer(borderLayer)
在您的情况下,动态标签的文本可能存在问题:如果文本是例如900000 它将绘制在边框下。要解决这个问题,您可以将 UILAbel 放在另一个视图(将包含形状和边框调整)中并对其进行布局。
示例:
Structure and constraints
What i got: container BG - orange, border - white, superview's BG - red
Controller 的 viewDidLoad 方法代码:
override func viewDidLoad() {
super.viewDidLoad()
self.view.backgroundColor = UIColor.red
self.containerView.backgroundColor = UIColor.orange
self.label.backgroundColor = UIColor.clear
self.label.textAlignment = .center
self.label.adjustsFontSizeToFitWidth = true
self.label.text = "9000000"
//Create Border and shape and apply it to container view
let borderLayer = CAShapeLayer()
let shapeLayer = CAShapeLayer()
let path = UIBezierPath(roundedRect: containerView.bounds, cornerRadius: containerView.bounds.width / 2).cgPath
//Set this rounding path to both layers
shapeLayer.path = path
borderLayer.path = path
//adjust border layer
borderLayer.lineWidth = 20
borderLayer.strokeColor = UIColor.white.cgColor
//apply shape layer as mask to your view, it will cut your view by the corners
self.containerView.layer.mask = shapeLayer
//Set fill color for border layer as clear, because technically it just puts colored layer over your view
borderLayer.fillColor = UIColor.clear.cgColor
//Add border layer as sublayer to your view's main layer
self.containerView.layer.addSublayer(borderLayer)
}
另一个解决方案是完全忘记不完美的 borderWidth
并在彼此内部使用 两个视图:
extension UIView {
func roundBounds() {
layer.cornerRadius = frame.size.height / 2
clipsToBounds = true
}
}
class RoundLabel: UILabel {
override func awakeFromNib() {
super.awakeFromNib()
roundBounds()
}
}
class RoundView: UIView {
override func awakeFromNib() {
super.awakeFromNib()
roundBounds()
}
}
谜底已解。
很好的解决方案
添加一个 1 像素的描边,masksToBounds
将完成正确剪裁边缘的工作:
override func draw(_ rect: CGRect) {
super.draw(rect)
// workaround incomplete borders:
UIColor(cgColor: layer.borderColor!).setStroke()
let path = UIBezierPath(roundedRect: bounds, cornerRadius: layer.cornerRadius)
path.lineWidth = 1
path.stroke()
}
解释
实际上,根据我的测试,设置 layer.borderWidth = 5
等同于公式:
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()
但另一方面,layer.cornerRadius = frame.size.height / 2
+ layer.masksToBounds = true
将使用不同的未知方法进行裁剪,该方法在边缘具有不同的别名公式。因为裁剪和绘图没有相同的别名,所以有一些像素显示背景颜色而不是边框颜色。