swiftuikitcore-graphicsuibezierpathquadratic-curve

UIBezierPath not equally fat everywhere and cut off


I am trying to draw a line with a smooth quadratic curve attached to its end. Therefore, I am using this code:

import Foundation
import UIKit

class IndicatingView: UIView {
    var path: UIBezierPath!

    override init(frame: CGRect) {
        super.init(frame: frame)
    }

    func createLineWithCurve() {
        path = UIBezierPath()

        path.move(to: CGPoint(x: 0, y: 0))

        path.addLine(to: CGPoint(x: 0, y: self.frame.height - self.frame.width))

        path.addQuadCurve(to: CGPoint(x: self.frame.width, y: self.frame.height), controlPoint: CGPoint(x: 0, y: self.frame.height))
    }

    override func draw(_ rect: CGRect) {
        self.createLineWithCurve()

        path.lineWidth = 3
        // Specify a border (stroke) color.
        UIColor.purple.setStroke()
        path.stroke()
    }

    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
}

And the output I get looks quite good already:

Output

However, as you can see, the quad-curve is fatter than the normal line. Also, the quad-curve seems to be cut off at the bottom. How can I fix that? I want the path to be completely smooth without looking fatter at some points or being cut-off somehow.

Thanks in advance for your help :)


Solution

  • The problem is that your path is getting clipped by the bounds of the view. For example, in your vertical stroke starting at 0, 0, half of the path’s vertical stroke will fall outside of the left edge of the view.

    You should use insetBy(dx:dy:) to inset the stroked path by half the line width, which will ensure the whole stroke will fall within the bounds of the view. And then use minX, maxX, etc. of that resulting inset CGRect to figure out the various CGPoint for your path.

    For example:

    class IndicatingView: UIView {
        func lineWithCurve() -> UIBezierPath {
            let lineWidth: CGFloat = 3
            let rect = bounds.insetBy(dx: lineWidth / 2, dy: lineWidth / 2)
    
            let path = UIBezierPath()
            path.lineWidth = lineWidth
            path.lineCapStyle = .square
    
            path.move(to: CGPoint(x: rect.minX, y: rect.minY))            
            path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY - rect.width))
            path.addQuadCurve(to: CGPoint(x: rect.maxX, y: rect.maxY), controlPoint: CGPoint(x: rect.minX, y: rect.maxY))
    
            return path
        }
    
        override func draw(_ rect: CGRect) {
            UIColor.purple.setStroke()
            lineWithCurve().stroke()
        }
    }
    

    That yields:

    enter image description here