Swift Custom Pie Chart - Strange behavior cutting transparent circle from multiple UIBezierPaths

Viewed 517

Creating a custom pie chart / doughnut style graph with Swift, and am running into a strange problem when trying to cut the hole out of the doughnut. I've tried variations on center and radius for the second UIBezierPath, but I haven't been able to accomplish a clean cut hole from the center. Any help would be greatly appreciated.

Subclass of UIView:

import UIKit

public class DoughnutView: UIView {

public var data: [Float]? {
    didSet { setNeedsDisplay() }
}

public var colors: [UIColor]? {
    didSet { setNeedsDisplay() }
}

@IBInspectable public var spacerWidth: CGFloat = 2 {
    didSet { setNeedsDisplay() }
}

@IBInspectable public var thickness: CGFloat = 20 {
    didSet { setNeedsDisplay() }
}

public override func draw(_ rect: CGRect) {
    guard data != nil && colors != nil else {
        return
    }

    guard data?.count == colors?.count else {
        return
    }

    let center = CGPoint(x: bounds.size.width / 2.0, y: bounds.size.height / 2.0)
    let radius = min(bounds.size.width, bounds.size.height) / 2.0
    let total = data?.reduce(Float(0)) { $0 + $1 }
    var startAngle = CGFloat(Float.pi)

    UIColor.clear.setStroke()

    for (key, value) in data!.enumerated() {
        let endAngle = startAngle + CGFloat(2.0 * Float.pi) * CGFloat(value / total!)

        let doughnut = UIBezierPath()
        doughnut.move(to: center)
        doughnut.addArc(withCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)

        let hole = UIBezierPath(arcCenter: center, radius: radius - thickness, startAngle: startAngle, endAngle: endAngle, clockwise: true)
        hole.move(to: center)
        doughnut.append(hole)
        doughnut.usesEvenOddFillRule = true

        doughnut.close()
        doughnut.lineWidth = spacerWidth
        colors?[key].setFill()

        doughnut.fill()
        doughnut.stroke()

        startAngle = endAngle
    }
}

public override func layoutSubviews() {
    super.layoutSubviews()
    self.backgroundColor = UIColor.clear
    setNeedsDisplay()
}
}

And then a ViewController:

class ViewController: UIViewController {

@IBOutlet weak var doughnut: DoughnutView!

override func viewDidLoad() {
    super.viewDidLoad()

    doughnut.data = [3, 14, 5]
    doughnut.colors = [UIColor.red, UIColor.yellow, UIColor.blue]

    view.backgroundColor = .purple

}

}

The result:

result

1 Answers
Related