Swift snippet to create a pie chart using CAShapeLayer and UIKit

October 10, 20243 min read#snippet, #swift, #uikit

This Swift code snippet is a minimal runable code that renders a pie chart using CAShapeLayer and UIKit.

Code

PieChartsView

final public class PieChartsView: UIView {
    private var data: [CGFloat]
    private var shapeLayers: [CAShapeLayer] = []
    
    public init(data: [CGFloat]) {
        self.data = data
        super.init(frame: .zero)
        backgroundColor = .clear
        setupGestureRecognizers()
    }
    
    required init?(coder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    public override func draw(_ rect: CGRect) {
        guard !data.isEmpty else { return }
        
        let total = data.reduce(0, +)
        let center = CGPoint(x: rect.width / 2, y: rect.height / 2)
        let radius: CGFloat = min(rect.width, rect.height) / 2 - 20
        let space: CGFloat = 0.1 // Space between arcs in radians
        var startAngle: CGFloat = 0

        for value in data {
            let endAngle = startAngle + (value / total) * (2 * .pi - CGFloat(data.count) * space)
            let shapeLayer = CAShapeLayer()
            let path = UIBezierPath(arcCenter: center, radius: radius, startAngle: startAngle, endAngle: endAngle, clockwise: true)
            shapeLayer.path = path.cgPath
            shapeLayer.strokeColor = UIColor.random().cgColor
            shapeLayer.fillColor = UIColor.clear.cgColor
            shapeLayer.lineWidth = 4
            shapeLayer.lineCap = .round

            layer.addSublayer(shapeLayer)
            shapeLayers.append(shapeLayer)
            startAngle = endAngle + space
        }
    }
    
    private func setupGestureRecognizers() {
        let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
        addGestureRecognizer(tapGestureRecognizer)
    }
    
    @objc private func handleTap(_ gesture: UITapGestureRecognizer) {
        let location = gesture.location(in: self)
        let touchBuffer: CGFloat = 10.0 // Buffer area around the arc

        for shapeLayer in shapeLayers {
            if let path = shapeLayer.path {
                let strokedPath = path.copy(strokingWithWidth: shapeLayer.lineWidth + touchBuffer, lineCap: .round, lineJoin: .round, miterLimit: 0)
                if strokedPath.contains(location) {
                    shapeLayer.lineWidth = 8
                    shapeLayer.setNeedsDisplay()
                } else {
                    shapeLayer.lineWidth = 4
                    shapeLayer.setNeedsDisplay()
                }
            }
        }
    }
}

private extension UIColor {
    static func random() -> UIColor {
        return UIColor(
            red: .random(in: 0...1),
            green: .random(in: 0...1),
            blue: .random(in: 0...1),
            alpha: 1.0
        )
    }
}

Sample ViewController

class TestVC: UIViewController {
    override func viewDidLoad() {
        super.viewDidLoad()

        let containerView = UIView()
        containerView.backgroundColor = .clear
        view.addSubview(containerView)
        
        containerView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor),
            containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor),
            containerView.widthAnchor.constraint(equalToConstant: 250),
            containerView.heightAnchor.constraint(equalToConstant: 250)
        ])
        
        let pieChartView = PieChartsView(data: [10, 20, 30, 40, 50, 60])
        containerView.addSubview(pieChartView)
        
        pieChartView.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            pieChartView.topAnchor.constraint(equalTo: containerView.topAnchor),
            pieChartView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
            pieChartView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
            pieChartView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor)
        ])

        // Create the first label
        let label1 = UILabel()
        label1.text = "Total sum:"
        label1.font = .systemFont(ofSize: 12)
        label1.textColor = .gray
        label1.textAlignment = .center
        containerView.addSubview(label1)
        
        let label2 = UILabel()
        label2.text = "284.45USD"
        label2.font = .boldSystemFont(ofSize:18)
        label2.textColor = .black
        containerView.addSubview(label2)
        
        label1.translatesAutoresizingMaskIntoConstraints = false
        label2.translatesAutoresizingMaskIntoConstraints = false
        NSLayoutConstraint.activate([
            label1.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
            label1.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: -10),
            label2.centerXAnchor.constraint(equalTo: containerView.centerXAnchor),
            label2.centerYAnchor.constraint(equalTo: containerView.centerYAnchor, constant: 10)
        ])
    }
}

Result

Quick Drop logo

Profile picture

Personal blog by An Tran. I'm focusing on creating useful apps.
#Swift #Kotlin #Mobile #MachineLearning #Minimalist


© An Tran - 2024