Search code examples
iosswiftuiviewuikitxib

Adding border with width to UIView show small background outside


I'm trying to add circle border to a UIView with green background, I created simple UIView subclass with borderWidth, cornerRadius and borderColor properties and I'm setting it from storyboard.

@IBDesignable
class RoundedView: UIView {

    @IBInspectable var cornerRadius: CGFloat {
        get {
            return layer.cornerRadius
        }
        set {
            layer.cornerRadius = newValue
            layer.masksToBounds = newValue > 0
        }
    }

    @IBInspectable var borderWidth: CGFloat {
        get {
            return layer.borderWidth
        }
        set {
            layer.borderWidth = newValue
        }
    }

    @IBInspectable var borderColor: UIColor {
        get {
            if let color = layer.borderColor {
                return UIColor(cgColor: color)
            } else {
                return UIColor.clear
            }
        }
        set {
            layer.borderColor = newValue.cgColor
        }
    } 

}

But when I compile and run an app or display it in InterfaceBuilder I can see a line outside the border that is still there (and is quite visible on white background).

enter image description here

This RoundedView with green background, frame 10x10, corner radius = 5 is placed in corner of plain UIImageView (indicates if someone is online or not). You can see green border outside on both UIImageView and white background.

enter image description here

Can you please tell me what's wrong?


Solution

  • What you are doing is relying on the layer to draw your border and round the corners. So you are not in charge of the result. You gave it a green background, and now you are seeing the background "stick out" at the edge of the border. And in any case, rounding the corners is a really skanky and unreliable way to make a round view. To make a round view, make a round mask.

    So, the way to make your badge is to take complete charge of what it is drawn: you draw a green circle in the center of a white background, and mask it all with a larger circle to make the border.

    Here is a Badge view that will do precisely what you're after, with no artifact round the outside:

    class Badge : UIView {
        class Mask : UIView {
            override init(frame:CGRect) {
                super.init(frame:frame)
                self.isOpaque = false
                self.backgroundColor = .clear
            }
            required init?(coder aDecoder: NSCoder) {
                fatalError("init(coder:) has not been implemented")
            }
            override func draw(_ rect: CGRect) {
                let con = UIGraphicsGetCurrentContext()!
                con.fillEllipse(in: CGRect(origin:.zero, size:rect.size))
            }
        }
        let innerColor : UIColor
        let outerColor : UIColor
        let innerRadius : CGFloat
        var madeMask = false
        init(frame:CGRect, innerColor:UIColor, outerColor:UIColor, innerRadius:CGFloat) {
            self.innerColor = innerColor
            self.outerColor = outerColor
            self.innerRadius = innerRadius
            super.init(frame:frame)
        }
        required init?(coder aDecoder: NSCoder) {
            fatalError("init(coder:) has not been implemented")
        }
        override func draw(_ rect: CGRect) {
            let con = UIGraphicsGetCurrentContext()!
            con.setFillColor(outerColor.cgColor)
            con.fill(rect)
            con.setFillColor(innerColor.cgColor)
            con.fillEllipse(in: CGRect(
                x: rect.midX-innerRadius, y: rect.midY-innerRadius,
                width: 2*innerRadius, height: 2*innerRadius))
            if !self.madeMask {
                self.madeMask = true // do only once
                self.mask = Mask(frame:CGRect(origin:.zero, size:rect.size))
            }
        }
    }
    

    I tried this with a sample setting as follows:

    let v = Badge(frame: CGRect(x:100, y:100, width:16, height:16), 
                  innerColor: .green, outerColor: .white, innerRadius: 5)
    self.view.addSubview(v)
    

    It looks fine. Adjust the parameters as desired.