Search code examples
iosswiftuibezierpathcashapelayerswift3

CAShapeLayer missing pixel in top-left corner


I'm using a CAShapeLayer with a UIBezierPath to draw a bordered box in a view.

This is working fine, but the very first pixel (top, left) is not drawn.

This is my code:

let focusSize = CGRect(x: focusX, y: focusY, width: focusWidth, height: focusHeight)
let focusPath = UIBezierPath(roundedRect: focusSize, cornerRadius: 0)

let borderLayer = CAShapeLayer()
borderLayer.path = focusPath.cgPath
borderLayer.fillColor = UIColor.clear.cgColor
borderLayer.strokeColor = UIColor.white.cgColor
borderLayer.lineWidth = 2
borderLayer.frame = self.someView.bounds
self.someView.layer.addSublayer(borderLayer)

The result (note the pixel in the top, left corner):

Weird pixel

I thought this might be related to antialiasing, but playing around with the x, y and borderWidth does not seem to fix the issue. Does anyone know what is causing this?


Solution

  • For some reason when you create a path using (roundedRect rect: CGRect, cornerRadius: CGFloat) it is not closed (despite what it says in the documentation).

    Calling focusPath.close() closes the path and removes the missing pixel.

    However, if you don't want rounded corners just use a normal rect and it will draw correctly.

    let focusPath = UIBezierPath(rect: focusSize)

    For anyone interested here are the resulting paths:

    UIBezierPath(roundedRect:  CGRect(x: 10, y: 10, width: 100, height: 100), cornerRadius: 0)
    <UIBezierPath: 0x6180000b8000; <MoveTo {10, 10}>,
     <LineTo {110, 10}>,
     <LineTo {110, 110}>,
     <LineTo {10, 110}>,
     <LineTo {10, 10}>,
     <LineTo {10, 10}>
    
    //After calling path.close()
    <UIBezierPath: 0x6180000b8000; <MoveTo {10, 10}>,
     <LineTo {110, 10}>,
     <LineTo {110, 110}>,
     <LineTo {10, 110}>,
     <LineTo {10, 10}>,
     <LineTo {10, 10}>,
     <Close>
    
     UIBezierPath(rect: CGRect(x: 10, y: 10, width: 100, height: 100))
    <UIBezierPath: 0x6180000b7f40; <MoveTo {10, 10}>,
     <LineTo {110, 10}>,
     <LineTo {110, 110}>,
     <LineTo {10, 110}>,
     <Close>
    

    Setting the lineCap may make the box appear correctly, but isn't fixing the issue as it just extends the visible end points and covers up the missing bit. Set borderLayer.lineJoin = kCALineJoinBevel to see why this can be a problem.