カテゴリー
iOS Swift

UIBezierPathのaddArcの使い方

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)
    }
  }
}