圆形边框不能完美地剪裁/遮罩

时间:2018-02-07 07:50:42

标签: ios border calayer clip

我在Interface Builder中制作了一个标签,其中包含固定高度和固定宽度的约束:
enter image description here

我将它子类化为白色圆形边框:

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
    }
}

但是剪辑/屏蔽在运行时并不好:
enter image description here

我期待一个完美的白色边框,没有橙色像素。

iPhone 8(模拟器和真实设备),iOS 11.2,Xcode 9.2,Swift 3.2

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

3 个答案:

答案 0 :(得分:1)

您应该使用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)
    }

答案 1 :(得分:0)

另一个解决方案是完全忘记不完美的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()
    }
}

circular border effect

答案 2 :(得分:0)

神秘解决了。

很好的解决方案

添加1个像素的笔划,masksToBounds将完成剪裁边缘的工作:

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()
}

说明

实际上,根据我的测试,设置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将使用不同的未知方法进行剪辑,该方法在边缘上具有不同的锯齿公式。由于剪裁和绘图没有相同的锯齿,因此有些像素显示背景颜色而不是边框​​颜色。