Home > Software design >  UIBezierPath rounded corner becoming circular
UIBezierPath rounded corner becoming circular

Time:12-02

I am facing a weird issue. I don't know if I am making some mistake or it is a fault with UIBezierPath.

I want to have corner radius of UIBezierPath around 40 percent of width or height, but the issue is as soon as I increase 0.1% corner radius it becomes circular. for eg. it displays rounded corner for 0.327% but as soon as I make it 0.328% it becomes circular.

Please let me know what I can do to solve my problem. Current scenario with corner percentage

Class for my view is below.

class MyView: UIView {
    var fraction : CGFloat = 0.5 {
        didSet {
            progressBorder.strokeEnd = fraction
        }
    }
    
    var strokeWidth : CGFloat = 4.0 {
        didSet {
            
        }
    }
    @IBInspectable var cornerPercentage : CGFloat = 0.327
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        layer.cornerRadius = frame.height * cornerPercentage
//        backgroundColor = .red
    }
    
    let progressBorder = CAShapeLayer()
    
    
    override func draw(_ rect: CGRect) {

        let cornerrad : CGFloat = (frame.height) * cornerPercentage
        layer.borderColor = UIColor.systemPink.cgColor
        
        layer.cornerRadius = cornerrad
        let border = CAShapeLayer()
        // make sure this path coincides with the border of the view
        border.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerrad).cgPath
        border.cornerRadius = cornerrad
        border.strokeStart = 0
        border.strokeEnd = 1
        border.strokeColor = AppColor.fieldsBackground().cgColor
        border.lineWidth = strokeWidth
        border.fillColor = nil
        
        // make sure this path coincides with the border of the view
        progressBorder.path = UIBezierPath(roundedRect: bounds, cornerRadius: cornerrad).cgPath
        progressBorder.cornerRadius = cornerrad
        progressBorder.lineJoin = .miter
        progressBorder.lineCap = .round
        progressBorder.strokeStart = 0
        progressBorder.strokeEnd = fraction
        
        progressBorder.strokeColor = AppColor.buttonsPrimary().cgColor
        progressBorder.lineWidth = strokeWidth
        progressBorder.fillColor = nil
        layer.addSublayer(border)
        layer.addSublayer(progressBorder)
        backgroundColor = .white
    }
}

I want a rounded progress view with 40% of square view's height or width

CodePudding user response:

If you follow the link in my comment, you'll see discussion of the "bug" in UIBezierPath(roundedRect: ...).

Here's a modified version of your MyView - with cornerPercentage = 0.4 - that avoids that bug:

class MyView: UIView {
    var fraction : CGFloat = 0.5 {
        didSet {
            progressBorder.strokeEnd = fraction
        }
    }
    
    var strokeWidth : CGFloat = 4.0 {
        didSet {
            border.lineWidth = strokeWidth
            progressBorder.lineWidth = strokeWidth
        }
    }
    @IBInspectable var cornerPercentage : CGFloat = 0.4 // 0.327
    
    let border = CAShapeLayer()
    let progressBorder = CAShapeLayer()
    
    override init(frame: CGRect) {
        super.init(frame: frame)
        commonInit()
    }
    
    required init?(coder: NSCoder) {
        super.init(coder: coder)
        commonInit()
    }
    
    private func commonInit() {
        layer.addSublayer(border)
        layer.addSublayer(progressBorder)

        border.lineWidth = strokeWidth
        progressBorder.lineWidth = strokeWidth

        border.fillColor = nil
        progressBorder.fillColor = nil

        layer.borderColor = UIColor.systemPink.cgColor
    }
    
    override func layoutSubviews() {
        super.layoutSubviews()
        
        let cornerrad : CGFloat = (bounds.height) * cornerPercentage
        
        layer.cornerRadius = cornerrad
        
        let r: CGRect = bounds
        
        // center points for the corner arcs
        let ptCTR: CGPoint = CGPoint(x: r.maxX - cornerrad, y: r.minY   cornerrad)
        let ptCBR: CGPoint = CGPoint(x: r.maxX - cornerrad, y: r.maxY - cornerrad)
        let ptCBL: CGPoint = CGPoint(x: r.minX   cornerrad, y: r.maxY - cornerrad)
        let ptCTL: CGPoint = CGPoint(x: r.minX   cornerrad, y: r.minY   cornerrad)
        
        let bez: UIBezierPath = UIBezierPath()
        
        // Top-Right corner
        bez.addArc(withCenter: ptCTR, radius: cornerrad, startAngle: .pi * 1.5, endAngle: .pi * 0.0, clockwise: true)
        
        // Bottom-Right corner
        bez.addArc(withCenter: ptCBR, radius: cornerrad, startAngle: .pi * 0.0, endAngle: .pi * 0.5, clockwise: true)
        
        // Bottom-Left corner
        bez.addArc(withCenter: ptCBL, radius: cornerrad, startAngle: .pi * 0.5, endAngle: .pi * 1.0, clockwise: true)
        
        // Top-Left corner
        bez.addArc(withCenter: ptCTL, radius: cornerrad, startAngle: .pi * 1.0, endAngle: .pi * 1.5, clockwise: true)
        
        // close the path
        bez.close()
        
        // make sure this path coincides with the border of the view
        border.path = bez.cgPath
        border.strokeStart = 0
        border.strokeEnd = 1
        border.strokeColor = UIColor.lightGray.cgColor // AppColor.fieldsBackground().cgColor
        
        // make sure this path coincides with the border of the view
        progressBorder.path = bez.cgPath
        progressBorder.strokeStart = 0
        progressBorder.strokeEnd = fraction
        
        progressBorder.strokeColor = UIColor.systemBlue.cgColor // AppColor.buttonsPrimary().cgColor
    }
}

and an example view controller -- fraction starts at 0.1 and increases by 0.1 with each tap:

class ViewController: UIViewController {
    
    let myTestView = MyView()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        myTestView.translatesAutoresizingMaskIntoConstraints = false
        view.addSubview(myTestView)
        
        let g = view.safeAreaLayoutGuide
        NSLayoutConstraint.activate([
            
            myTestView.centerXAnchor.constraint(equalTo: g.centerXAnchor, constant: 0.0),
            myTestView.centerYAnchor.constraint(equalTo: g.centerYAnchor, constant: 0.0),
            myTestView.widthAnchor.constraint(equalToConstant: 300.0),
            myTestView.heightAnchor.constraint(equalToConstant: 50.0),
            
        ])
        
        myTestView.fraction = 0.1

    }
    
    override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
        myTestView.fraction  = 0.1
    }
    
}

As a side note: you'll see I moved your code around a bit. You can add the shape layers and set their non-changing properties during init, then set the path(s) and other changing properties in layoutSubviews(). It's not a good idea to be adding sublayers inside draw().

CodePudding user response:

The above answer worked with corner radius. But now I want the progress to be start from exact center. Currently It's starting from a point away from center.

Example

The progress strokeEnd in image is 0.5.

I want something like this

  • Related