作業4:利用 UIBezierPath 實現圓環進度條,甜甜圈圖表 & 圓餅圖

Baaro
彼得潘的 Swift iOS / Flutter App 開發教室
15 min readJul 22, 2020

USE Playground.

Photo by William Iven on Unsplash

圓環進度條(circular progress ring)

import UIKit
let aDegree = CGFloat.pi / 180
let lineWidth: CGFloat = 10
let radius: CGFloat = 50
let startDegree: CGFloat = 270
let circlePath = UIBezierPath(ovalIn: CGRect(x: lineWidth, y: lineWidth, width: radius*2, height: radius*2))
let circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.strokeColor = UIColor.gray.cgColor
circleLayer.lineWidth = lineWidth
circleLayer.fillColor = UIColor.clear.cgColor
let percentage: CGFloat = CGFloat.random(in: 1...100)
let percentagePath = UIBezierPath(arcCenter: CGPoint(x: lineWidth + radius, y: lineWidth + radius), radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * (startDegree + 360 * percentage / 100), clockwise: true)
let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath
percentageLayer.strokeColor = UIColor(red: CGFloat.random(in: 0...1), green: CGFloat.random(in: 0...1), blue: CGFloat.random(in: 0...1), alpha: 1).cgColor
percentageLayer.lineWidth = lineWidth
percentageLayer.fillColor = UIColor.clear.cgColor
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 2*(radius+lineWidth), height: 2*(radius+lineWidth)))
label.textAlignment = .center
label.text = String(format:"%.2f %", percentage)
label.textColor = UIColor.white
let view = UIView(frame: label.frame)
view.layer.addSublayer(circleLayer)
view.layer.addSublayer(percentageLayer)
view.addSubview(label)
view

非整圈都進度條

import UIKit
let aDegree = CGFloat.pi / 180
let lineWidth: CGFloat = 10
let radius: CGFloat = 50
//設定起始結束角度
let startDegree: CGFloat = 150
let endDegree:CGFloat = 30
let circlePath = UIBezierPath(arcCenter: CGPoint(x: lineWidth + radius, y: lineWidth + radius), radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)
let circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath
circleLayer.strokeColor = UIColor.gray.cgColor
circleLayer.lineWidth = lineWidth
circleLayer.fillColor = UIColor.clear.cgColor
circleLayer.lineCap = .round
//數值隨機
let percentage: CGFloat = CGFloat.random(in: 0...100)
var defRedVal: CGFloat = 1
var defGreenVal: CGFloat = 1
if percentage <= 50{
defGreenVal -= abs(percentage-50)*2/100
}else{
defRedVal -= abs(percentage-50)*2/100
}
//set color
var showColor:UIColor = UIColor(red: defRedVal, green: defGreenVal, blue: 0, alpha: 1)
let percentagePath = UIBezierPath(arcCenter: CGPoint(x: lineWidth + radius, y: lineWidth + radius), radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * (startDegree + 240 * percentage / 100), clockwise: true)
let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath
percentageLayer.strokeColor = showColor.cgColor
percentageLayer.lineWidth = lineWidth
percentageLayer.fillColor = UIColor.clear.cgColor
percentageLayer.lineCap = .round
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 2*(radius+lineWidth), height: 2*(radius+lineWidth)))
label.textAlignment = .center
label.text = String(format:"%.2f %", percentage)
label.textColor = showColor
let view = UIView(frame: label.frame)
view.layer.addSublayer(circleLayer)
view.layer.addSublayer(percentageLayer)
view.addSubview(label)
view

調整起始結束的數值 150~30

畫圖時的圈角度 240

留下半邊60度不顯示。

甜甜圈圖表(donut chart)

import UIKit
let aDegree = CGFloat.pi / 180
let lineWidth: CGFloat = 50
let radius: CGFloat = 100
var startDegree: CGFloat = 270
let view = UIView(frame: CGRect(x: 0, y: 0, width: 2*(radius+lineWidth), height: 2*(radius+lineWidth)))
let center = CGPoint(x: lineWidth + radius, y: lineWidth + radius)
var percentages: [CGFloat] = []
var totalFloat:CGFloat = 100.0
for i in 1...3{
var number:CGFloat
if i==3{
number = totalFloat
}else{
number = CGFloat.random(in: CGFloat(totalFloat/3) ... totalFloat/2)
}
percentages.append(number)
totalFloat -= number
}
for percentage in percentages {
let endDegree = startDegree + 360 * percentage / 100
let percentagePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)
let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath
let setColor = UIColor(red: CGFloat.random(in: 0...0.7), green: CGFloat.random(in: 0...0.7), blue: CGFloat.random(in: 0...0.7), alpha: 1)
percentageLayer.strokeColor = setColor.cgColor
percentageLayer.lineWidth = lineWidth
percentageLayer.fillColor = UIColor.clear.cgColor
view.layer.addSublayer(percentageLayer)
let label = createLabel(percentage:percentage,startDegree:startDegree,setColor:setColor)
view.addSubview(label)
startDegree = endDegree
}
func createLabel(percentage: CGFloat, startDegree: CGFloat, setColor: UIColor) -> UILabel {
let textCenterDegree = startDegree + 360 * percentage / 2 / 100
let textPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: aDegree * textCenterDegree, endAngle: aDegree * textCenterDegree, clockwise: true)
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 30))
label.backgroundColor = setColor.withAlphaComponent(0.7)
label.font = UIFont.systemFont(ofSize: lineWidth/2)
label.text = String(format:"%.2f %", percentage)
label.textColor = UIColor.white
label.sizeToFit()
label.center = textPath.currentPoint
return label
}
view

圓餅圖(pie chart)

import UIKit
let aDegree = CGFloat.pi / 180
let radius: CGFloat = 50
var startDegree: CGFloat = 270
let view = UIView(frame: CGRect(x: 0, y: 0, width: 2*(radius), height: 2*(radius)))
var percentages: [CGFloat] = []
var totalFloat:CGFloat = 100
for i in 1...3{
var number:CGFloat
if i==3{
number = totalFloat
}else{
number = CGFloat.random(in: CGFloat(totalFloat/3) ... totalFloat/2)
}
percentages.append(number)
totalFloat -= number
}
let lineWidth: CGFloat = 20
let center = view.center
for percentage in percentages {
let endDegree = startDegree + 360 * percentage / 100
let percentagePath = UIBezierPath()
percentagePath.move(to: view.center)
percentagePath.addArc(withCenter: view.center, radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)
let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath
let setColor = UIColor(red: CGFloat.random(in: 0...0.7), green: CGFloat.random(in: 0...0.7), blue: CGFloat.random(in: 0...0.7), alpha: 1)
percentageLayer.fillColor = setColor.cgColor
view.layer.addSublayer(percentageLayer)
let label = createLabel(percentage:percentage,startDegree:startDegree,setColor:setColor)
view.addSubview(label)
startDegree = endDegree
}
func createLabel(percentage: CGFloat, startDegree: CGFloat, setColor: UIColor) -> UILabel {
let textCenterDegree = startDegree + 360 * percentage / 2 / 100
let textPath = UIBezierPath(arcCenter: center, radius: radius/2, startAngle: aDegree * textCenterDegree, endAngle: aDegree * textCenterDegree, clockwise: true)
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 30))
label.font = UIFont.systemFont(ofSize: lineWidth/2)
label.text = String(format:"%.2f %", percentage)
label.textColor = UIColor.white
label.sizeToFit()
label.center = textPath.currentPoint
return label
}
view

實際上都是從大大那邊抄來的,

就加了點變化而已。

參考連結

--

--