Search code examples
iosswiftuibuttonbordercalayer

How to initialize button borders generated by code In Swift 4


I used the code to create the button border.

I have to change the shape of the button border for some reason.

However, the border generated by mask is not initialized with the following code.

button.backgroundColor = .clear
button.layer.CornerRadius = 0

In ViewController.swift :

@IBOutlet weak var btnDelete: UIButton!

func FirstChange() {
    btnDelete.layer.borderWidth = 0
    btnDelete.layer.cornerRadius = 0
    btnDelete.layer.borderColor = UIColor(rgb: 0xFFFFFF).cgColor
    // Draw the border again
    btnDelete.round(corners: [.topRight, .bottomRight], radius: 50, borderColor: UIColor(rgb: 0xced4da), borderWidth: 1)
}

func SecChange() {
    btnDelete.backgroundColor = .clear // not work
    // Draw the border again
    btnDelete.layer.borderColor = UIColor(rgb: 0xced4da).cgColor
    btnDelete.layer.borderWidth = 1
    btnDelete.layer.cornerRadius = 18
}

In UIView.swift :

func round(corners: UIRectCorner, radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
    let mask = _round(corners: corners, radius: radius)
    addBorder(mask: mask, borderColor: borderColor, borderWidth: borderWidth)
}

@discardableResult func _round(corners: UIRectCorner, radius: CGFloat) -> CAShapeLayer {
    let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
    let mask = CAShapeLayer()
    mask.path = path.cgPath
    self.layer.mask = mask
    return mask
}

func addBorder(mask: CAShapeLayer, borderColor: UIColor, borderWidth: CGFloat) {
    let borderLayer = CAShapeLayer()
    borderLayer.path = mask.path
    borderLayer.fillColor = UIColor.clear.cgColor
    borderLayer.strokeColor = borderColor.cgColor
    borderLayer.lineWidth = borderWidth
    borderLayer.frame = bounds
    layer.addSublayer(borderLayer)
}

The second time draw the border(run SecChange()), it overlaps with the first border.

Please help me to initialize the first border I painted.

(Running SecChange() and running FirstChange() initializes the border successfully.)


Solution

  • since you are adding a CAShapeLayer to the UIButton you need to remove this layer from the button. For this you could give the layer a name and add a new method to remove the layer and call that new method in your second change. Additionally you should remove the border layer when calling round(corners:radius:borderColor:borderWidth:) again, otherwise you would end up with another layer on top.

    func SecChange() {
            btnDelete.removeBorderLayer() //remove border layer if existing
            // Draw the border again
            btnDelete.layer.borderColor = UIColor.gray.cgColor
            btnDelete.layer.borderWidth = 1
            btnDelete.layer.cornerRadius = 18
        }
    
    extension UIView {
        func round(corners: UIRectCorner, radius: CGFloat, borderColor: UIColor, borderWidth: CGFloat) {
            let mask = _round(corners: corners, radius: radius)
            addBorder(mask: mask, borderColor: borderColor, borderWidth: borderWidth)
        }
    
        @discardableResult func _round(corners: UIRectCorner, radius: CGFloat) -> CAShapeLayer {
            let path = UIBezierPath(roundedRect: bounds, byRoundingCorners: corners, cornerRadii: CGSize(width: radius, height: radius))
            let mask = CAShapeLayer()
            mask.path = path.cgPath
            self.layer.mask = mask
            return mask
        }
    
        func addBorder(mask: CAShapeLayer, borderColor: UIColor, borderWidth: CGFloat) {
            removeBorderLayer()
            let borderLayer = CAShapeLayer()
            borderLayer.name = "borderLayer"
            borderLayer.path = mask.path
            borderLayer.fillColor = UIColor.clear.cgColor
            borderLayer.strokeColor = borderColor.cgColor
            borderLayer.lineWidth = borderWidth
            borderLayer.frame = bounds
            layer.addSublayer(borderLayer)
        }
    
        func removeBorderLayer() {
            if let borderLayer = layer.sublayers?.first(where: { $0.name == "borderLayer" }) {
                borderLayer.removeFromSuperlayer()
            }
        }
    }
    

    Best, Carsten