さて今回はiOSのヘルスケアアプリで見かけるような始点・終点を丸くした円グラフを作ってみました。
そうこんなやつです。
環境:swift 4.2、Xcode 10.1
#円グラフを描画するUIViewの作成
まず円グラフを描画するUIViewを作成します。
円グラフの描画はCAShapeLayerとUIBezierPathを使用して行います。
import UIKit
class ChartView:UIView {
let caShapeLayerForBase:CAShapeLayer = CAShapeLayer.init()
let caShapeLayerForValue:CAShapeLayer = CAShapeLayer.init()
func drawChart(rate:Double){
//グラフを表示
drawBaseChart()
drawValueChart(rate: rate)
//グラフをアニメーションで表示
let caBasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
caBasicAnimation.duration = 2.0
caBasicAnimation.fromValue = 0.0
caBasicAnimation.toValue = 1.0
caShapeLayerForValue.add(caBasicAnimation, forKey: "chartAnimation")
}
/**
円グラフの軸となる円を表示
*/
private func drawBaseChart(){
let shapeFrame = CGRect.init(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
caShapeLayerForBase.frame = shapeFrame
caShapeLayerForBase.strokeColor = UIColor(displayP3Red: 1, green: 0.8, blue: 0.4, alpha: 1.0).cgColor
caShapeLayerForBase.fillColor = UIColor.clear.cgColor
caShapeLayerForBase.lineWidth = 50
caShapeLayerForBase.lineCap = .round
let startAngle:CGFloat = CGFloat(0.0)
let endAngle:CGFloat = CGFloat(Double.pi * 2.0)
caShapeLayerForBase.path = UIBezierPath.init(arcCenter: CGPoint.init(x: shapeFrame.size.width / 2.0, y: shapeFrame.size.height / 2.0),radius: shapeFrame.size.width / 2.0,startAngle: startAngle,endAngle: endAngle,clockwise: true).cgPath
self.layer.addSublayer(caShapeLayerForBase)
}
/**
円グラフの値を示す円(半円)を表示
@param rate 円グラフの値の%値
*/
private func drawValueChart(rate:Double){
//CAShareLayerを描く大きさを定義
let shapeFrame = CGRect.init(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
caShapeLayerForValue.frame = shapeFrame
//CAShareLayerのデザインを定義
caShapeLayerForValue.strokeColor = UIColor(displayP3Red: 1, green: 0.4, blue: 0.4, alpha: 1).cgColor
caShapeLayerForValue.fillColor = UIColor.clear.cgColor
caShapeLayerForValue.lineWidth = 50
caShapeLayerForValue.lineCap = .round
//開始位置を時計の0時の位置にする
let startAngle:CGFloat = CGFloat(-1 * Double.pi / 2.0)
//終了位置を時計の0時起点で引数渡しされた割合の位置にする
let endAngle :CGFloat = CGFloat(rate / 100 * Double.pi * 2.0 - (Double.pi / 2.0))
//UIBezierPathを使用して半円を定義
caShapeLayerForValue.path = UIBezierPath.init(arcCenter: CGPoint.init(x: shapeFrame.size.width / 2.0, y: shapeFrame.size.height / 2.0),radius: shapeFrame.size.width / 2.0,startAngle: startAngle,endAngle: endAngle,clockwise: true).cgPath
self.layer.addSublayer(caShapeLayerForValue)
}
}
##解説
###CAShapeLayerの設定
まずCAShapeLayerのframeで大きさを定義します。今回はUIViewと同じ大きさにしておきます。
let caShapeLayerForValue:CAShapeLayer = CAShapeLayer.init()
---------- (中略) -----------
//CAShareLayerを描く大きさを定義
let shapeFrame = CGRect.init(x: 0, y: 0, width: self.frame.width, height: self.frame.height)
caShapeLayerForValue.frame = shapeFrame
続いて円のデザイン設定を行います。
caShapeLayerForValue.strokeColor = UIColor(displayP3Red: 1, green: 0.4, blue: 0.4, alpha: 1).cgColor //円の線の色
caShapeLayerForValue.fillColor = UIColor.clear.cgColor //円の中の色->透明に設定して線しか表示されないようにする
caShapeLayerForValue.lineWidth = 50 //線の太さ
caShapeLayerForValue.lineCap = .round //線の始点・終点が丸くなる様に指定
今回のテーマである円グラフの始点と終点を丸くする設定はCAShapeLayerのlineCapプロパティにCAShapeLayerLineCap.round
を指定するだけで実現できちゃいました。
その後、CAShareLayerのpathに円(半円)を描画するpathを渡します。
円(半円)を描画するpathはUIBezierPathを使用して作成します。
//UIBezierPathを使用して半円を定義
caShapeLayerForValue.path = UIBezierPath.init(arcCenter: CGPoint.init(x: shapeFrame.size.width / 2.0, y: shapeFrame.size.height / 2.0),radius: shapeFrame.size.width / 2.0,startAngle: startAngle,endAngle: endAngle,clockwise: true).cgPath
arcCenterに円(半円)の中心点となる座標を、radiusに円(半円)の半径を指定します。
重要なのは円(半円)の開始位置を示すstartAngleと終了位置を示すendAngleです。
startAngleは0を渡すと時計の3時の位置を指します。
そしてDouble.pi * 2で一周する(360度回る)様になっています。
その為、時計の0時から開始させたい時は1/4円ほどマイナス方向へ動かすのでCGFloat(-1 * Double.pi / 2.0)
を指定します。
終了位置は円1周(Double.pi * 2
)の何%の位置かを示しますのでrate / 100 * Double.pi * 2.0
で表現できます(rateは%値)。時計の0時からの位置で示す場合は開始位置同様に1/4円ほどマイナス方向へ動かしCGFloat(rate / 100 * Double.pi * 2.0 - (Double.pi / 2.0))
とします。
最後のclockwiseはtrueを指定すると時計周り、falseを指定すると反時計周りになります。
###円グラフをアニメーションで動かす
CABasicAnimationを使用すると円(半円)の描画をアニメーションで行うことができます。
//グラフをアニメーションで表示
let caBasicAnimation = CABasicAnimation(keyPath: "strokeEnd")
caBasicAnimation.duration = 2.0
caBasicAnimation.fromValue = 0.0
caBasicAnimation.toValue = 1.0
caShapeLayerForValue.add(caBasicAnimation, forKey: "chartAnimation")
CABasicAnimationのkeyPathにはCAShapeLayerのstrokeEndプロパティを指定します。
durationにアニメーションを実行する時間を秒単位で指定します。
fromValueとtoValueはアニメーション開始時と終了時のCAShapeLayerのstrokeEndの値を指定しますが、今回のケースでは円の線の描画を途中から始めたり途中で止めたりすることはありませんので、fromValueは常に0.0、toValueは常に1.0で問題ありません。
#最後に
グラフを表示するUIView(このサンプルではChartView)をUIViewController内に貼り付けて、円グラフを描画するメソッド(このサンプルではdrawChartメソッド)を実行すれば円グラフが描画されます。円グラフの値はメソッドの引数で%値で渡しています。
import UIKit
class ViewController: UIViewController {
private let textRate:UITextView = UITextView()
private let labelRate:UILabel = UILabel()
private let buttonDraw:UIButton = UIButton()
private let chartView:ChartView = ChartView()
override func viewDidLoad() {
super.viewDidLoad()
textRate.layer.cornerRadius = 10
textRate.layer.borderColor = UIColor.lightGray.cgColor
textRate.layer.borderWidth = 0.5
textRate.keyboardType = .numberPad
textRate.text = "75" //とりあえずデフォル値は75%
textRate.font = UIFont.systemFont(ofSize: 16)
self.view.addSubview(textRate)
labelRate.text = "%"
self.view.addSubview(labelRate)
buttonDraw.setTitle("グラフ表示", for: .normal)
buttonDraw.setTitleColor(UIColor.blue, for: .normal)
buttonDraw.addTarget(self, action: #selector(self.touchUpButtonDraw), for: .touchUpInside)
self.view.addSubview(buttonDraw)
self.view.addSubview(chartView)
changeScreen()
}
/**
端末の向きの変更のイベント
*/
override func viewWillTransition(to size: CGSize, with coordinator: UIViewControllerTransitionCoordinator) {
super.viewWillTransition(to: size, with: coordinator)
coordinator.animate(
alongsideTransition: nil,
completion: {(UIViewControllerTransitionCoordinatorContext) in
self.changeScreen()
}
)
}
override func viewWillAppear(_ animated: Bool) {
drawChart()
}
private func changeScreen(){
let screenSize: CGRect = UIScreen.main.bounds
let widthValue = screenSize.width
let heightValue = screenSize.height
textRate.frame = CGRect(x: widthValue/2-170, y: 50, width: 100, height: 40)
labelRate.frame = CGRect(x: widthValue/2-70, y: 50, width: 40, height: 40)
buttonDraw.frame = CGRect(x: widthValue/2-30, y: 50, width: 200, height: 40)
var drawWidth = widthValue * 0.8
if (widthValue > heightValue){
drawWidth = heightValue * 0.8
}
chartView.frame = CGRect(x: widthValue/2-drawWidth/2, y: 150, width: drawWidth, height: drawWidth)
}
@objc func touchUpButtonDraw(){
drawChart()
}
/**
グラフを表示
*/
private func drawChart(){
let rate = Double(textRate.text!)
chartView.drawChart(rate: rate!)
}
}
#GitHub
今回紹介したサンプルコードはGitHubで公開しています。