addArc(withCenter: CGPoint, radius: CGFloat, startAngle: CGFloat, endAngle: CGFloat, clockwise: Bool)
addArchのstartAngelとendAngleは円上のradianを指定しますが、centerをcとすると、0と2piは下記の位置になります。そのため、円の一番上の頂点から円を描こうとすると、そのstartAngleは-CGFloat.pi/2とする必要があります。
-pi/2
|
|
|
----------c---------- 0 and 2pi
|
|
|
import UIKit
class ViewController: UIViewController {
var viewHeight: CGFloat {
view.bounds.height
}
var viewWidth: CGFloat {
view.bounds.width
}
var topInset: CGFloat {
view.safeAreaInsets.top
}
var bottomInset: CGFloat {
view.safeAreaInsets.bottom
}
var appAreaHeight: CGFloat {
viewHeight - topInset - bottomInset
}
var diameter: CGFloat {
if viewWidth > viewHeight {
return viewHeight*0.4
} else {
return viewWidth*0.4
}
}
var rotated: Bool = false
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
drawPieChart()
}
// this is called when rotate the screen
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
print("viewWillTransition called")
rotated = true
}
override func viewWillLayoutSubviews() { // view lifecycle [3]
super.viewWillLayoutSubviews()
print("viewWillLayoutSubviews called")
if rotated {
drawPieChart()
}
}
// test values for drawing a pieChart
var values: [Int:Int] = [1:2, 2:2, 3:1]
// to start drawing the arc from top of the circle
let initialRadianForArc = -CGFloat.pi/2
// to pass the starting point of the arc to next iteration
var nextRadianForArc: CGFloat = 0
// need to store these for clearing paths and layers when the screen rotates and transitions
var paths: [UIBezierPath] = []
var layers: [CAShapeLayer] = []
private func drawPieChart() {
// need to remove layers from its superLayer and clear the array
layers.forEach { layer in
layer.removeFromSuperlayer()
}
layers = []
paths = []
var totalValue = values.map({ $0.value }).reduce(0, +)
let pi2 = 2*CGFloat.pi
let center = CGPoint(x: viewWidth/2, y: topInset + appAreaHeight/2)
for (i, dic) in values.enumerated() {
if i == 0 {
let path = UIBezierPath()
path.move(to: center)
let theta = pi2*(CGFloat(dic.value)/CGFloat(totalValue))
let startAngle = initialRadianForArc
let endAngle = startAngle + theta
nextRadianForArc = endAngle
path.addArc(withCenter: center, radius: diameter/2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.move(to: center)
paths.append(path)
} else {
let path = UIBezierPath()
path.move(to: center)
let theta = pi2*(CGFloat(dic.value)/CGFloat(totalValue))
let startAngle = nextRadianForArc
let endAngle = startAngle + theta
nextRadianForArc = endAngle
path.addArc(withCenter: center, radius: diameter/2, startAngle: startAngle, endAngle: endAngle, clockwise: true)
path.move(to: center)
paths.append(path)
}
}
// in case you want to animate the opacity...
let animation = CABasicAnimation(keyPath: "opacity")
animation.fromValue = 0
animation.toValue = 1
animation.duration = 1.2
paths.forEach{ path in
let shapeLayer = CAShapeLayer()
let red = CGFloat.random(in: 0...255)
let green = CGFloat.random(in: 0...255)
let blue = CGFloat.random(in: 0...255)
shapeLayer.path = path.cgPath
shapeLayer.lineWidth = 3
shapeLayer.lineCap = .round
shapeLayer.lineJoin = .round
shapeLayer.strokeColor = UIColor.systemGray.cgColor
shapeLayer.fillColor = UIColor(red: red/255, green: green/255, blue: blue/255, alpha: 1).cgColor
shapeLayer.add(animation, forKey: "opacity")
layers.append(shapeLayer)
view.layer.addSublayer(shapeLayer)
}
}
}