Search code examples
iosswift3uikitcore-graphicsuibezierpath

Incomplete UIBezierPath Stroke


I'm creating a Split Button on iOS using two UIButtons placed next to each other. Like so:

enter image description here

As you can see, there's a small bite taken out of the right button's top left corner which is undesirable. I'd like to get that part of the stroke complete.

The right button uses a UIBezierPath as a sublayer to draw a border around the button:

let borderWidth = CGFloat(4.0)
let borderLayer = CAShapeLayer()

var borderFrame = button.bounds.insetBy(dx: borderWidth/2.0, dy: borderWidth/2.0)

borderLayer.frame = borderFrame
borderFrame.origin = CGPoint.zero

borderLayer.path = UIBezierPath(roundedRect: borderFrame, byRoundingCorners: [.topRight, .bottomRight], cornerRadii: radius).cgPath

borderLayer.lineWidth = borderWidth
borderLayer.fillColor = UIColor.white.cgColor
borderLayer.strokeColor = UIColor.green.cgColor

button.layer.addSublayer(borderLayer)

If I use

borderLayer.path = CGPath(rect: borderFrame, transform: nil)

instead of the UIBezierPath, it works fine. And if I round the top left corner of the UIBezierPath it also works fine.

Can anyone help me figure out how to draw the border the way I'd like to? Thanks in advance!


Solution

  • Although UIBezierPath(roundedRect:byRoundingCorners:cornerRadii:) is documented to return a closed subpath, it doesn't, in my testing:

    import UIKit
    
    let path = UIBezierPath(roundedRect: CGRect(x: 0, y: 0, width: 40, height: 40), byRoundingCorners: [.topRight, .bottomRight], cornerRadii: CGSize(width: 10, height: 10))
    print(path)
    

    Output:

    <UIBezierPath: 0x6180000a5d00; <MoveTo {0, 0}>,
     <LineTo {24.713349999999998, 0}>,
     <CurveTo {33.300654247944713, 0.65495893024402596} {29.115070423814711, 0} {31.315930559369978, 0}>,
     <LineTo {33.685062071690076, 0.74911387847016009}>,
     <CurveTo {39.250886121529845, 6.3149379283099272} {36.27176173374253, 1.6905955604436995} {38.30940443955631, 3.7282382662574722}>,
     <CurveTo {40, 15.286649847295823} {40, 8.6840694406300223} {40, 10.884929576185289}>,
     <LineTo {40, 24.713349999999998}>,
     <CurveTo {39.345041069755972, 33.300654247944713} {40, 29.115070423814711} {40, 31.315930559369978}>,
     <LineTo {39.250886121529845, 33.685062071690076}>,
     <CurveTo {33.685062071690076, 39.250886121529838} {38.30940443955631, 36.27176173374253} {36.27176173374253, 38.309404439556296}>,
     <CurveTo {24.713350152704177, 40} {31.315930559369978, 40} {29.115070423814711, 40}>,
     <LineTo {0, 40}>,
     <LineTo {0, 0}>,
     <LineTo {0, 0}>
    

    It has a line from the last corner back to the first, but that is not the same as a closed path. There's no joint at the first corner, so it draws overlapping line caps instead of a single joint.

    Try this:

    let path = UIBezierPath(roundedRect: borderFrame, byRoundingCorners: [.topRight, .bottomRight], cornerRadii: radius)
    path.close()
    borderLayer.path = path.cgPath