來畫個精美的百分比圓餅圖吧

Swift精進第003天-運用UIBezierPath及function編寫百分比圓餅圖

作業參考:

彼得潘的文章有細部說明UIBezierPath的用法及圓餅圖的起始角度等基本觀念,這邊就不贅述了~

這篇作業的學習目標還額外新增了幾個東西:

  1. 使用function來定義繪圖參數
  2. 使用到極座標
  3. 不要平的進度條,末端改為圓弧狀的

Function部分定義如下:

//可自訂的「圓環寬度」、「色彩」、「百分比」func circleRatio(width: CGFloat, colorRed: CGFloat, colorGreen: CGFloat, colorBlue: CGFloat, percentageOrg: CGFloat)
{}

極座標的部分找來找去都沒有,所以就直接在直角座標上寫入轉換極座標的公式吧~

一般直角座標是:P ( x , y )
也就是從原點向右位移x,再向上位移y
(不過在iOS當中的y是上下相反,所以是向下位移y)

而極座標是:P ( r , θ )
也就是從原點距離為r,且與x軸夾角為θ的地方

細節可以參考維基百科

圓形末端的進度條樣式剛好可以套用極座標的用法,想辦法在進度條末端的中心點上畫上一個一樣寬的圓形即可,雖然講起來很簡單,不過中間卻還是因為座標的問題想了一陣子才好不容易搞定

程式碼如下,使用Playground

var view = UIView()//建立一個畫圓型比例的Function
func circleRatio(width: CGFloat, colorRed: CGFloat, colorGreen: CGFloat, colorBlue: CGFloat, percentageOrg: CGFloat)
{
//設定一些參數
let aDegree = CGFloat.pi/180
//一般習慣從12點方向畫圓,所以從iOS座標的270度開始(iOS座標與卡式座標上下顛倒)
let startpointDegree: CGFloat = 270
let circlelineWidth: CGFloat = width
let radius: CGFloat = 50
let percentage = percentageOrg - (width / 2 / 2 / radius * 100 * 0.2)
let percentageDegree = aDegree * (startpointDegree + 360 * percentage / 100)
//畫出百分比圖中心的圓
let innercirclePath = UIBezierPath(ovalIn: CGRect(x: circlelineWidth, y: circlelineWidth, width: radius*2, height: radius*2))
//建立圖層
let circleLayer = CAShapeLayer()
//將繪圖路徑設至圖層路徑上
circleLayer.path = innercirclePath.cgPath
//使用輸入的資料設定百分比線條,及線條色,內圓色
circleLayer.strokeColor = UIColor(red: 0.7, green: 0.7, blue: 0.7, alpha: 1).cgColor
circleLayer.lineWidth = circlelineWidth
circleLayer.fillColor = UIColor.clear.cgColor
//畫百分比外框
let circleCenter = CGPoint(x: circlelineWidth + radius, y: circlelineWidth + radius)
let percentagePath = UIBezierPath(arcCenter: circleCenter, radius: radius, startAngle: aDegree * startpointDegree, endAngle: percentageDegree, clockwise: true)
let percentageLayer = CAShapeLayer()
//將路徑放入圖層,設定外框及顏色
percentageLayer.path = percentagePath.cgPath
percentageLayer.strokeColor = UIColor(red: colorRed, green: colorGreen, blue: colorBlue, alpha: 1).cgColor
percentageLayer.lineWidth = circlelineWidth
percentageLayer.fillColor = UIColor.clear.cgColor
//開始繪製末端的圓形
let endpointPath = UIBezierPath(ovalIn: CGRect(x: radius + circlelineWidth + cos(percentageDegree) * radius - width/2, y: radius + circlelineWidth + sin(percentageDegree) * radius - width/2, width: width, height: width))
let endpointLayer = CAShapeLayer()
endpointLayer.path = endpointPath.cgPath
//因為長外框的話會造成畫出來的球比實際的還大,所以此處把外框設為沒有顏色
endpointLayer.strokeColor = UIColor.clear.cgColor
endpointLayer.fillColor = UIColor(red: colorRed, green: colorGreen, blue: colorBlue, alpha: 1).cgColor
//加入標籤
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 2*(radius+circlelineWidth), height: 2*(radius+circlelineWidth)))
label.textAlignment = .center
label.text = "\(percentageOrg)%"
view = UIView(frame: label.frame)
//加入圖層及subview
view.layer.addSublayer(circleLayer)
view.layer.addSublayer(percentageLayer)
view.layer.addSublayer(endpointLayer)
view.addSubview(label)
}

補充說明一下有點容易忘掉的地方:
繪圖流程:UIBezierPath → CAShapeLayer → UIView
CAShapeLayer進入UIView是使用.layer.addSublayer
但是把UILabel(屬於UIView)放進UIView時要使用.addSubview
因為2者本來就是不同東西,自然是使用不同方式

執行結果:

circleRatio(width: 35, colorRed: 0.8, colorGreen: 0.4, colorBlue: 0.4, percentageOrg: 70)
view
circleRatio(width: 15, colorRed: 0.5, colorGreen: 0.9, colorBlue: 0.7, percentageOrg: 20)
view
circleRatio(width: 25, colorRed: 0.3, colorGreen: 0.6, colorBlue: 0.8, percentageOrg: 50)
view

--

--

賽思Sethought
彼得潘的 Swift iOS / Flutter App 開發教室

一隻co-founder但不太懂分工,涉足研發.採購.製程.業務.設計.行銷;一位工程師但不學無術,略懂cad.cam.PS.IL.PR.swift.db。內向.喜思考.樂遊戲.愛動漫.重健康,只是位功不成名不就的小小貨色