製作按鈕輪盤

Eason
彼得潘的 Swift iOS / Flutter App 開發教室
10 min readApr 11, 2023

先在 controller 拉線

@IBOutlet weak var wheelUIImage: wheelUIImage!

創一個新的 UIImageView,讓UIImage的Class換成剛創新的UIImageView

ViewController 這邊新增按鈕,這邊名稱都要一樣

    @IBAction func startWheel(_ sender: Any) {
wheelUIImage.rotateGradually { resule in
let alertContoller = UIAlertController(title: "你轉到了\(resule)區塊", message: nil, preferredStyle: .alert)
let okAction = UIAlertAction(title: "確認", style: .cancel, handler: nil)
alertContoller.addAction(okAction)
self.present(alertContoller,animated: true)

}
}

回到UIImageView,在UIImageView裡面新增一個圓,並且把數字黏在上面,在這邊我們需要 override layoutSubviews,才能在裡面畫圖形,使用CAShapeLayer()最終把圓給畫出來。

在文字這邊比較特別,以往都用 UILabel,但這次有問GPT他建議使用CATextLayer(),用裡面的一些功能一樣實現UILabel的文字,而且在旋轉時比較不會出。

    let aDegree = CGFloat.pi / 180
let lineWidth: CGFloat = 20
let radius: CGFloat = 130
var startDegree: CGFloat = 270
override func layoutSubviews() {
//要讓圓形在layer的中心,可以使用bounds的屬性來計算出center的位置。
let center = CGPoint(x: bounds.width/2, y: bounds.height/2)
let percentages: [CGFloat] = [8.33, 8.33, 8.33, 8.33, 8.33,8.33, 8.33, 8.33, 8.33, 8.33, 8.33, 8.33]

for (i,percentage) in percentages.enumerated() {
let endDegree = startDegree + 360 * percentage / 100
let percentagePath = UIBezierPath()
//從圓的中心開始畫線
percentagePath.move(to: center)
//畫出扇形角度
percentagePath.addArc(withCenter: center, radius: radius, startAngle: aDegree * startDegree, endAngle: aDegree * endDegree, clockwise: true)
let percentageLayer = CAShapeLayer()
percentageLayer.path = percentagePath.cgPath
if(i % 2 == 0){
percentageLayer.fillColor = UIColor(ciColor: .red).cgColor
}else {
percentageLayer.fillColor = UIColor(ciColor: .black).cgColor
}
let percentageTextLayer = createLabel(text: "\(i + 1)", center: center, startDegree: startDegree, endDegree: endDegree)
percentageLayer.addSublayer(percentageTextLayer)
layer.addSublayer(percentageLayer)

startDegree = endDegree
}

}

func createLabel(text: String, center: CGPoint, startDegree: CGFloat, endDegree: CGFloat) -> CATextLayer {
let textCenterDegree = startDegree + (endDegree - startDegree) / 2
//文字也像圓形
let textPath = UIBezierPath(arcCenter: center, radius: radius * 0.9, startAngle: aDegree * textCenterDegree, endAngle: aDegree * textCenterDegree, clockwise: true)
let percentageLable = CATextLayer()
percentageLable.string = text
percentageLable.fontSize = 16
percentageLable.foregroundColor = UIColor.white.cgColor
percentageLable.alignmentMode = .center
percentageLable.frame = CGRect(x: 0, y: 0, width: 30, height: 20)
percentageLable.position = textPath.currentPoint
// 根據文字所在角度計算旋轉角度
// 讓數字永遠朝著圓心
let labelDegree = textCenterDegree - 270
let rotationAngle = aDegree * labelDegree
// 將旋轉運用在圖片上
percentageLable.transform = CATransform3DMakeRotation(rotationAngle, 0, 0, 1)
return percentageLable
}

參考學長姐們做出圖片旋轉的動畫並做了一些調整:

 var currentValue: Double = 0
func rotateGradually(handler:@escaping (String) -> ()) {
var result = ""
let randomDouble = Double.random(in: 0..<2 * Double.pi) // 產生0~2pi隨機的Double數字,也就是0~360度。
let rotateAnimation = CABasicAnimation(keyPath: "transform.rotation")
CATransaction.begin()
rotateAnimation.fromValue = currentValue
currentValue = currentValue + 100 * Double.pi + randomDouble //開始到結束之,總共了50圈加上randomDouble度。
let value = currentValue.truncatingRemainder(dividingBy: Double.pi * 2) //取得currentale/Doublepi*2餘數
let degree = value * 180 / Double.pi //將弧度轉成角度
switch degree{
case -30..<0:
result="1"
case 0..<30:
result="12"
case 30..<60:
result="11"
case 60..<90:
result="10"
case 90..<120:
result="9"
case 120..<150:
result="8"
case 150..<180:
result="7"
case 180..<210:
result="6"
case 210..<240:
result="5"
case 240..<270:
result="4"
case 270..<300:
result="3"
case 300..<330:
result="2"
case 330..<360:
result="1"
default:
result="...未知"
}
rotateAnimation.toValue = currentValue
rotateAnimation.isRemovedOnCompletion = false //動畫結束後仍保在結束狀態,讓轉盤不會在動畫結束時回到最初狀態。便繼再次轉動。
rotateAnimation.fillMode = .forwards
rotateAnimation.duration = 5 //動畫持續時間
rotateAnimation.repeatCount = 1 // 重複幾次
CATransaction.setCompletionBlock { //跑完動後要做的事
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3){//動畫結束後暫停0.3秒
handler(result)
}
}
rotateAnimation.timingFunction = CAMediaTimingFunction(controlPoints: 0, 0.9, 0.4, 1.00)//用cubic Bezier curve決定動畫速率曲線
//也可以用內建的easeOut,但我想要最後轉一點
self.layer.add(rotateAnimation, forKey: nil)
CATransaction.commit()
}

參考

作品:

GitHub:

--

--