#3_UIBezier & Circle 圓餅圖製作

圈圈圓圓圈圈

一個基本的圓形進度圖中間加上對應進度的 Label

試做圓餅圖系列,記錄一些我剛開始感到疑惑的問題。

Code 部分

func circle1(){
let aDegree = Double.pi / 180 // 將數學角度轉換為弧度
let lineWidth: Double = 10 // 定義線寬
let radius: Double = 50 // 定義半徑
let startDegree: Double = 270 // 起始角度

// 創建一個表示圓形的 UIBezierPath
let circlePath = UIBezierPath(ovalIn: CGRect(x: lineWidth, y: lineWidth, width: radius*2, height: radius*2))


// 創建一個用於繪製圓形的 CAShapeLayer
let circleLayer = CAShapeLayer()
circleLayer.path = circlePath.cgPath // 設定形狀為先前定義的圓形
circleLayer.strokeColor = UIColor(red: 199/255, green: 38/255, blue: 83/255, alpha: 0.5).cgColor // 設定線條顏色再轉換成 cgColor
circleLayer.lineWidth = lineWidth // 設定線條寬度
circleLayer.fillColor = UIColor.clear.cgColor // 設定圓形內部顏色為透明

let percentage: CGFloat = 80 // 要顯示的百分比
let endDegree = startDegree + 360 * percentage / 100 // 根據百分比計算結束角度

// 創建一個表示百分比的 UIBezierPath
let percentagePath = UIBezierPath(arcCenter: CGPoint(x: lineWidth + radius, y: lineWidth + radius), radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)

// 創建一個用於繪製百分比的 CAShapeLayer
let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath // 設定形狀為先前定義的百分比
percentageLayer.strokeColor = UIColor(red: 199/255, green: 38/255, blue: 83/255, alpha: 1).cgColor // 設定線條顏色再轉換成 cgColor
percentageLayer.lineWidth = lineWidth // 設定線條寬度
percentageLayer.fillColor = UIColor.clear.cgColor // 設定內部顏色為透明
percentageLayer.lineCap = .round // 設定線條的端點樣式為圓形

let viewWidth = 2*(radius + lineWidth) // 計算視圖的寬度

// 創建一個 UIView 來包含兩個 CAShapeLayer
let customView = UIView(frame: CGRect(x: 0, y: 0, width: viewWidth, height: viewWidth))
customView.layer.addSublayer(circleLayer) // 添加圓形層
customView.layer.addSublayer(percentageLayer) // 添加百分比層

// 創建一個 UILabel 來顯示百分比
let label = UILabel(frame: customView.bounds)
label.text = "\(percentage)%" // 設定標籤的文字為百分比
label.textAlignment = .center // 設定文字對齊方式為居中
customView.addSubview(label) // 將標籤添加到自定義視圖

// 將自定義視圖添加到父視圖
self.view.addSubview(customView)

// 設定自定義視圖的位置,使其位於父視圖的中心的 1/6 處
customView.center = CGPoint(x: self.view.frame.size.width / 2, y: self.view.frame.size.height / 6)
}

角度( 弧度 )
在這行 let percentagePath = UIBezierPath(arcCenter: CGPoint(x: lineWidth + radius, y: lineWidth + radius), radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)
裡面的 startAngle & endAngle 是圓的角度,需要一個像是弧度的單位,可以用以下的方法讓單位變成弧度。

let aDegree = Double.pi / 180 // 將數學角度轉換為弧度

所以現在可以想像 aDegree 是弧度 1 了。

位置
創建一個還未存在的圓,可以先想像有一個半徑 radius = 50*2 的圓,這個圓的邊緣距離無形的 X & Y 有一個 lineWidth = 10 的距離

// 創建一個表示圓形的 UIBezierPath
let circlePath = UIBezierPath(ovalIn: CGRect(x: lineWidth, y: lineWidth, width: radius*2, height: radius*2))
let percentagePath = UIBezierPath(arcCenter: CGPoint(x: lineWidth + radius, y: lineWidth + radius), radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)

一樣是創建一個還未存在的圓,只是生成的方法變成了用點座標生成,如果想要讓它準確的印在上面的那個圓的話就需要輸入上面那個圓的中心的 X & Y位置,就會是 (無形的 X & Y lineWidth = 10) + (圓的半徑 radius = 50 )
這樣就到達了圓中心點,之後輸入它的半徑、起始角度與結束角度
半徑關乎這個圓的大小
圓的角度可以參考以下

由此可以知道為何是 let startDegree: Double = 270 // 起始角度
結束角度則是以此起始角度為基礎往下算
let percentage: CGFloat = 80
let endDegree = startDegree + 360 * percentage / 100
意思就是起始圓的 (270 度)再加上(進度 80 換算成的圓的度數)
別忘了起始&結束角度參數都要先換成弧度所以前面都要先乘
aDegree 這個變數。

線寬
再來説說圓的線條寬度是如何配置,簡單來說就是圓裡跟圓外都會增加寬度數值平均分配裡外。

percentageLayer.lineWidth = lineWidth // 設定線條寬度
左邊 lineWidth = 10,右邊 lineWidth = 20

一開始創建 let lineWidth: Double = 10 的這個用於“線寬”和“ X & Y 間距”也能有效避免圓的線寬與創建的 view 切齊,能夠讓圓線寬與 view 的邊緣永遠保持半個線寬距離,這樣線寬也不會被切掉。

AddSubview
再來是創建一個 view 把兩個圓都塞進來
2*(radius + lineWidth) 正好可以讓塞進來的圓放在正中間。

let viewWidth = 2*(radius + lineWidth) // 計算視圖的寬度

// 創建一個 UIView 來包含兩個 CAShapeLayer
let customView = UIView(frame: CGRect(x: 0, y: 0, width: viewWidth, height: viewWidth))

第二種圓

Code

// 設定一個角度為圓周的 1/180,即一度所對應的弧度
let aDegree = Double.pi / 180
// 設定線寬為 25
let lineWidth: Double = 25
// 設定半徑為 50
let radius: Double = 50
// 設定起始角度為 270 度,這表示從圓的上方開始(0 度和 360 度位於圓的右側,90 度位於圓的下方,180 度位於圓的左側)
var startDegree: Double = 270
// 設定視圖寬度為兩倍的(半徑加上線寬)
let viewWidth = 2*(radius + lineWidth)
// 創建一個自定義的 UIView,大小由上述的 viewWidth 定義
let customView = UIView(frame: CGRect(x: 0, y: 0, width: viewWidth, height: viewWidth))
// 設定圓心為線寬加上半徑
let center = CGPoint(x: lineWidth + radius, y: lineWidth + radius)
// 定義一個陣列 percentages,裡面包含了每個段落的比例(百分比)
var percentages: [Double] = [30, 30, 40]
// 進行一個迴圈,每次迴圈根據 percentages 中的百分比來創建一個圓弧路徑
for percentage in percentages {
// 計算出結束的角度,起始角度加上該段落應有的角度
let endDegree = startDegree + 360 * percentage / 100
// 創建一個 UIBezierPath,這個路徑是一個從 startDegree 到 endDegree 的圓弧
let percentagePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)
// 創建一個 CAShapeLayer,將路徑設定為剛才創建的 percentagePath
let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath
// 設定該 layer 的描繪顏色為隨機顏色
percentageLayer.strokeColor = UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1).cgColor
// 設定該 layer 的線寬
percentageLayer.lineWidth = lineWidth
// 設定該 layer 的填充顏色為透明
percentageLayer.fillColor = UIColor.clear.cgColor
// 將該 layer 加到 customView 的 layer 上
customView.layer.addSublayer(percentageLayer)
// 將 startDegree 設定為 endDegree,讓下一個圓弧從這裡開始
startDegree = endDegree
}
// 設定 customView 的背景色為藍色
customView.backgroundColor = .blue
// 將 customView 加到當前的 view 上
self.view.addSubview(customView)
// 將 customView 的中心點設定為當前 view 寬度的一半、高度的 2.5 分之一,即將 customView 放在上半部的中心位置
customView.center = CGPoint(x: self.view.frame.size.width / 2, y: self.view.frame.size.height / 2.5)

}

是運用陣列將每一段弧度填入

var percentages: [Double] = [30, 30, 40]
// 進行一個迴圈,每次迴圈根據 percentages 中的百分比來創建一個圓弧路徑
for percentage in percentages {
// 計算出結束的角度,起始角度加上該段落應有的角度
let endDegree = startDegree + 360 * percentage / 100

將上一個結束的角度留著給下一個做延續使用

// 將 startDegree 設定為 endDegree,讓下一個圓弧從這裡開始
startDegree = endDegree

第三種圓

Code

func circle3() {
// 定義一度所對應的弧度值
let aDegree = Double.pi / 180
// 定義線寬為 25
let lineWidth: Double = 25
// 定義圓弧的半徑為 50
let radius: Double = 50
// 起始角度定義為 270度,這表示從圓的上方開始
var startDegree: Double = 270
// 視圖的寬度定義為兩倍的(半徑加線寬)
let viewWidth = 2 * (radius + lineWidth)
// 創建一個自定義的 UIView,大小由上述的 viewWidth 定義
let customView = UIView(frame: CGRect(x: 0, y: 0, width: viewWidth, height: viewWidth))
// 定義圓心點,其位置為半徑與線寬的和
let center = CGPoint(x: radius + lineWidth, y: radius + lineWidth)
// 定義一個陣列 percentages,裡面包含了每個圓弧的比例
let percentages: [Double] = [30, 30, 40]

// 定義一個函數來創建標籤,標籤顯示的是圓弧的比例,並位於每個圓弧的中心點
func circle3CreateLabel(percentage: Double, startDegree: Double) -> UILabel {
// 計算出標籤的中心點應在的角度,即起始角度加上該圓弧角度的一半
let textCenterDegree = startDegree + 360 * percentage / 2 / 100
// 用 UIBezierPath 創建一個路徑,該路徑是一個以圓心為中心,textCenterDegree 為角度,半徑為 radius 的點
let textPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: aDegree * textCenterDegree, endAngle: aDegree * textCenterDegree, clockwise: true)
// 創建一個標籤,並將其大小定義為 50x30
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 30))
// 設定標籤的背景為透明
label.backgroundColor = .clear
// 設定標籤的字體為系統字體,大小為 8
label.font = .systemFont(ofSize: 8)
// 設定標籤的文字為百分比
label.text = "\(percentage)%"
// 將標籤的大小調整為包裹其內容
label.sizeToFit()
// 將標籤的中心點設定為 textPath 的當前點,這樣標籤就會出現在每個圓弧的中心點
label.center = textPath.currentPoint
// 返回標籤
return label
}

// 進行一個迴圈,每次迴圈根據 percentages 中的百分比來創建一個圓弧
for percentage in percentages {
// 計算出結束的角度,起始角度加上該段落應有的角度
let endDegree = startDegree + 360 * percentage / 100
// 創建一個 UIBezierPath,這個路徑是一個從 startDegree 到 endDegree 的圓弧
let percentagePath = UIBezierPath(arcCenter: center, radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)
// 創建一個 CAShapeLayer,將路徑設定為剛才創建的 percentagePath
let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath
// 設定該 layer 的描繪顏色為隨機顏色
percentageLayer.strokeColor = UIColor(red: .random(in: 0...1), green: .random(in: 0...1), blue: .random(in: 0...1), alpha: 1).cgColor
// 設定該 layer 的線寬
percentageLayer.lineWidth = lineWidth
// 設定該 layer 的填充顏色為透明
percentageLayer.fillColor = UIColor.clear.cgColor
// 將該 layer 加到 customView 的 layer 上
customView.layer.addSublayer(percentageLayer)
// 使用 circle3CreateLabel 函數創建一個標籤,並添加到 customView 上
let label = circle3CreateLabel(percentage: percentage, startDegree: startDegree)
customView.addSubview(label)
// 將 startDegree 設定為 endDegree,讓下一個圓弧從這裡開始
startDegree = endDegree
}
// 設定 customView 的背景色為淺灰色
customView.backgroundColor = UIColor.lightGray
// 將 customView 加到當前的 view 上
self.view.addSubview(customView)
// 將 customView 的中心點設定為當前 view 寬度的一半、高度的 3/2,即將 customView 放在下半部的中心位置
customView.center = CGPoint(x: self.view.frame.size.width / 2, y: self.view.frame.size.height / 1.5)

}

創建了一個顯示 Label 的函數,一開始還會覺得奇怪起始角和結束角一樣是什麼意思?如果這兩個角度相同,就等於在畫一個點,而不是一條弧線。在這裡textPath並不是真正被用來創建顯示的弧線。它被用來計算出一個點的位置,這個點是標籤中心點應該在的位置。以下引用自彼得潘

我們希望在每一段圓弧的中心放上 label,因此我們利用 startDegree + 360 * percentage / 2 / 100 計算圓弧中心的角度,然後在 UIBezierPath 繪製圓弧時,在 startAngle & endAngle 傳入此角度,此時 path 的 currentPoint 即為 label 的中心座標。

第四種圓

基於上面的製作 label Code 中增加兩行

func circle3CreateLabel(percentage: Double, startDegree: Double) -> UILabel {
// 計算出標籤的中心點應在的角度,即起始角度加上該圓弧角度的一半
let textCenterDegree = startDegree + 360 * percentage / 2 / 100
// 用 UIBezierPath 創建一個路徑,該路徑是一個以圓心為中心,textCenterDegree 為角度,半徑為 radius 的點
// 起始和結束點如果這兩個角度相同,就等於在畫一個點,而不是一條弧線,在這裡textPath並不是真正被用來創建顯示的弧線。它被用來計算出一個點的位置,這個點是標籤中心點應該在的位置。
// textPath.currentPoint之後就能給我們這個點的座標,我們可以直接用這個座標來設置標籤的中心點。
// 所以即使startAngle和endAngle相同,我們仍然能得到我們需要的結果:一個圓周上某個特定角度的點的座標。
let textPath = UIBezierPath(arcCenter: center, radius: radius, startAngle: aDegree * textCenterDegree, endAngle: aDegree * textCenterDegree, clockwise: true)
// 創建一個標籤,並將其大小定義為 50x30
let label = UILabel(frame: CGRect(x: 0, y: 0, width: 50, height: 30))
// 設定標籤的背景為透明
label.backgroundColor = .clear
// 設定標籤的字體為系統字體,大小為 8
label.font = .systemFont(ofSize: 8)
// 設定標籤的文字為百分比
label.text = "\(percentage)%"
// 將標籤的大小調整為包裹其內容
label.sizeToFit()
// 將標籤的中心點設定為 textPath 的當前點,這樣標籤就會出現在每個圓弧的中心點
label.center = textPath.currentPoint

/*
textCenterDegree 是文字所在位置的角度,計算方法是將起始角度 startDegree 加上 360 * percentage / 2 / 100。
這個角度表示文字在甜甜圈圖表上的中心位置。

270 是圓環起始位置的角度。這是由於原始的程式碼中,圓環起始位置被設定為 270 度。
將這個角度用作參考點,以便計算標籤的旋轉角度。

labelDegree 是標籤的旋轉角度,計算方法是將 textCenterDegree 減去 270。
這樣做可以將標籤的角度轉換為相對於起始角度的偏移量。*/
let labelDegree = textCenterDegree - 270
label.transform = CGAffineTransform(rotationAngle: .pi / 180 * labelDegree)

/*
將標籤應用相應的旋轉角度。.pi / 180 是將角度轉換為弧度的因子,然後乘以 labelDegree 得到最終的旋轉角度。

通過這段程式碼,我們可以根據文字所在的角度將標籤旋轉到相應的位置,使其與甜甜圈圖表的區域對齊。
這樣可以確保標籤與圖表的對應部分保持一致並呈現正確的角度。*/


// 返回標籤
return label
}

這兩行

let labelDegree = textCenterDegree - 270
label.transform = CGAffineTransform(rotationAngle: .pi / 180 * labelDegree)

簡而言之就是 textCenterDegree 中包含的 (startDegree = 270),是基於要計算圓的起始點 270 而加上去的,而現在存粹是要算 label 跟著目前的角度的角度,所以把它減掉,大概是這樣 😂。

纏與發的高等應用技巧,運用圓

Slider 與圓框 進度條
var sliderInstance: Float = -90

/*
在這裡,sliderInstance 的初始值被設置為 -90,這是因為在繪製圓形時,我們通常會把圓形的開始點(也就是0度)設置在圓形的最頂點。
這個開始點對應於我們常說的「三點鐘方向」。然而,在繪圖的坐標系中,0度通常是指「三點鐘方向」,而不是「十二點鐘方向」。
所以,我們將 sliderInstance 初始設置為 -90,是為了讓圓形的開始點從「十二點鐘方向」開始,這樣就能更直觀地對應我們對於圓形度數的一般理解。
這裡的 -90 度就是在數學上的負90度位置,也就是「十二點鐘方向」。
這種設定在創建像時鐘、計時器或任何其他需要從「十二點鐘方向」開始的圓形介面的應用程序時都很常見。
*/


let circleLayer = CAShapeLayer()
let circleLayer2 = CAShapeLayer()


let percentageLabel = UILabel()

class CircleApplicationViewController: UIViewController {

@IBOutlet weak var sliderOut: UISlider!



override func viewDidLoad() {
super.viewDidLoad()



circle()
upLabel()


}


func circle(){
let aDegree: CGFloat = .pi / 180
let startPoint = CGPoint(x: 190, y: 300)
let radius: CGFloat = 50

//circle 1
let path = UIBezierPath()
path.move(to: startPoint)
path.addArc(withCenter: startPoint, radius: radius, startAngle: aDegree * 0, endAngle: aDegree * 360, clockwise: true)
path.close()

circleLayer.removeFromSuperlayer()
/*
.removeFromSuperlayer()
這個方法的作用是從它的父層(superlayer)中移除 circleLayer。
在這個程式碼中,每當滑動條的值改變時,circle() 函數就會被調用,這意味著新的圓形路徑和形狀層會被創建並添加到視圖的層上。
如果你不先從父層中移除舊的 circleLayer,新的圓形路徑就會被添加到舊的圓形路徑上,可能導致顏色,大小或位置的重疊和混亂。
因此,每次創建新的圓形路徑前,我們先把舊的 circleLayer 從它的父層中移除,這樣就能保證每次只有新的圓形路徑被添加到視圖的層上。
另外,這也是一種很好的內存管理策略,因為這樣可以確保任何不再需要的 circleLayer 都會被適時地移除,釋放出它所使用的資源。
*/
circleLayer.path = path.cgPath
circleLayer.fillColor = CGColor(red: 144/255, green: 64/255, blue: 173/255, alpha: 1)




//circle2 dynamic
let path2 = UIBezierPath()
//path2.move(to: startPoint)
path2.addArc(withCenter: startPoint, radius: radius+10, startAngle: aDegree * -90, endAngle: aDegree * CGFloat(sliderInstance), clockwise: true)
//path2.close()

circleLayer2.removeFromSuperlayer()
circleLayer2.path = path2.cgPath
circleLayer2.fillColor = UIColor.clear.cgColor
circleLayer2.lineWidth = 15
circleLayer2.strokeColor = CGColor(red: 1, green: 0, blue: 1, alpha: 1)



view.layer.addSublayer(circleLayer)
view.layer.addSublayer(circleLayer2)
upLabel()



}


@IBAction func sliderValueChange(_ sender: UISlider) {
sliderInstance = sliderOut.value
circle()
}

func upLabel() {
percentageLabel.frame = CGRect(x: 0, y: 0, width: 20, height: 40)
percentageLabel.backgroundColor = .clear
percentageLabel.textColor = .label
percentageLabel.textAlignment = .center
percentageLabel.font = .systemFont(ofSize: 8)
percentageLabel.sizeToFit()
percentageLabel.center = CGPoint(x: 190, y: 300)

let sliderInstancePercentage = (sliderInstance+90) / 360 * 100
percentageLabel.text = "\(String(format: "%.0f", sliderInstancePercentage))%"
view.addSubview(percentageLabel)
}



}

會有兩個圓

  1. 中間一個淡紫色不會動的圓
  2. 會隨著 slider 數值變化結束角度的圓

第二個圓重點

path2.addArc(withCenter: startPoint, radius: radius+10, startAngle: aDegree * -90, endAngle: aDegree * CGFloat(sliderInstance), clockwise: true)

半徑為第一圓 +10
結束角度為 slider 數值轉換而來

整體重點
將兩個圓和 label 共同塞進一個 Function

另外
因為每拉一次都會啟動 Function 製作一個新的圓,這樣兩個圓的圖層會一直累積,進而累積影響到記憶體,所以將兩個圖層的實例拉到 Instance scope,在每次要畫新的圖層時先把舊的移除,此外這樣當結束角度數值比上一次少時也能真的呈現出來

circleLayer.removeFromSuperlayer()
circleLayer.path = path.cgPath

circleLayer2.removeFromSuperlayer()
circleLayer2.path = path2.cgPath

其他
發現第二個圓在這樣的方式下可以不用

path2.move(to: startPoint)
path2.close()

應用圓 2,輸入數字產生圓餅圖

import UIKit

class CircleApplicationTwoViewController: UIViewController {


@IBOutlet weak var deviceTextField: UITextField!
@IBOutlet weak var placeTextField: UITextField!
@IBOutlet weak var mealsTextField: UITextField!
@IBOutlet weak var photographyTextField: UITextField!
@IBOutlet weak var lightingTextField: UITextField!
@IBOutlet weak var audioTextField: UITextField!

@IBOutlet weak var totalCost: UILabel!

var devicePercentage = 0.0
var placePercentage = 0.0
var mealsPercentage = 0.0
var photographyPercentage = 0.0
var lightingPercentage = 0.0
var audioPercentage = 0.0

let aDegree: Double = .pi / 180
let radius: Double = 80

var startDegree: Double = 270
var customview = UIView()


override func viewDidLoad() {
super.viewDidLoad()

let tap: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.dismissKeyboard))
view.addGestureRecognizer(tap)

}


@objc func dismissKeyboard() {
view.endEditing(true)
}




@IBAction func calculate(_ sender: UIButton) {
let radiuses = [radius, radius+10, radius+20, radius+30, radius+40, radius+50]
let colors: [UIColor] = [.cyan, .brown, .magenta, .orange, .purple, .label]


let deviceNum = Double(deviceTextField.text!)
let placeNum = Double(placeTextField.text!)
let mealsNum = Double(mealsTextField.text!)
let photographyNum = Double(photographyTextField.text!)
let lightingNum = Double(lightingTextField.text!)
let audioNum = Double(audioTextField.text!)

if deviceNum != nil, placeNum != nil, mealsNum != nil, photographyNum != nil, lightingNum != nil, audioNum != nil {
let totalSpend = deviceNum! + placeNum! + mealsNum! + photographyNum! + lightingNum! + audioNum!

devicePercentage = deviceNum! / totalSpend * 100
placePercentage = placeNum! / totalSpend * 100
mealsPercentage = mealsNum! / totalSpend * 100
photographyPercentage = photographyNum! / totalSpend * 100
lightingPercentage = lightingNum! / totalSpend * 100
audioPercentage = audioNum! / totalSpend * 100

totalCost.text = "總支出\(String(format: "%.f", totalSpend))"

}

customview.removeFromSuperview()
customview = UIView(frame: CGRect(x: 0, y: 0, width: radius * 2, height: radius * 2))
let percentages: [Double] = [devicePercentage, placePercentage, mealsPercentage, photographyPercentage, lightingPercentage, audioPercentage]
var arrayIndex = -1
var radiusIndex = -1
var colorIndex = -1
var colorTextIndex = -1

func labelCreate(percentage: Double, startDegree: Double) -> UILabel {
let textCenterDegree = startDegree + (percentage * 360) / 100 / 2
let textPath = UIBezierPath()
radiusIndex += 1
textPath.addArc(withCenter: customview.center, radius: radiuses[radiusIndex] , startAngle: aDegree * textCenterDegree, endAngle: aDegree * textCenterDegree, clockwise: true)
let label = UILabel.init(frame: CGRect(x: 0, y: 0, width: 20, height: 20))

let labelArrays = ["器材", "場地", "餐費", "攝影", "燈光", "收音"]
arrayIndex += 1

label.text = "\(String(format: "%.f", percentage))%"+"\(labelArrays[arrayIndex])"
colorTextIndex += 1
label.textColor = colors[colorTextIndex]
label.backgroundColor = .black
label.font = .systemFont(ofSize: 8)
label.sizeToFit()
label.center = textPath.currentPoint

let labelDegree = textCenterDegree - 270
label.transform = CGAffineTransform(rotationAngle: CGFloat.pi / 180 * labelDegree)

return label

}

for percentage in percentages {
let endDegree = startDegree + 360 * percentage / 100
let percentagePath = UIBezierPath()
percentagePath.move(to: customview.center)
percentagePath.addArc(withCenter: customview.center, radius: radius - 10, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)

let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath
colorIndex += 1
percentageLayer.fillColor = colors[colorIndex].cgColor
let label = labelCreate(percentage: percentage, startDegree: startDegree)
customview.layer.addSublayer(percentageLayer)
customview.addSubview(label)
startDegree = endDegree

}
self.view.addSubview(customview)
customview.center = CGPoint(x: 150, y: 400)

}

}

重點

將各個 TextField 、Button、Label 連結做好
將 6 個 TextField 的文字轉換成它們的百分比數字並將總和數字顯示在 Label
在 Button 裡完成顯示出該百分比的圓和圓上的 Label
建立一個陣列用於依序顯示圓上 Label 的高度
建立一個陣列用於依序顯示圓餅圖的顏色和圓上 Label 的顏色
建立一個陣列用於依序顯示與 TextField 對應的 Label 的文字內容
建立一個陣列用於依序傳入 TextField 轉換而來的結束角度
利用 for in 將TextField 轉換而來的結束角度依序傳入
每一個使用到的陣列的 Index 依序 +1
在最一開始的 view 產生前,先將前一個生出的 view 移除
view 產生的位置需注意不裁切
案件的出現與消失

參考

--

--