建立Time picker

UIKit內建的Date Picker只有顯示小時與分鐘,沒有秒數。所以使用Picker View進行客製化。

時間單位Label

如不定義何事label的寬度的話,寬度會被壓縮而無法完全顯示lable。

extension UIPickerView {
func setPickerLabelSize(labels: [Int: UILabel]) {
let fontSize: CGFloat = 20
let labelWidth: CGFloat = self.frame.width / CGFloat(self.numberOfComponents)
let y: CGFloat = (self.frame.height / 2) - (fontSize / 2)

for i in 0...self.numberOfComponents {
if let label = labels[i] {
label.frame = CGRect(x: labelWidth * CGFloat(i), y: y, width: labelWidth, height: fontSize)
label.font = UIFont.systemFont(ofSize: fontSize, weight: .medium)
label.backgroundColor = .clear
label.textAlignment = NSTextAlignment.left
self.addSubview(label)
}
}
}
}

就如table view一樣,picker view也需要代理。

let numbersOf24 = [Int](0...23)
let numbersOf60 = [Int](0...59)
extension TimerViewController: UIPickerViewDataSource, UIPickerViewDelegate {

包含數字與label總共6個欄位

func numberOfComponents(in pickerView: UIPickerView) -> Int {
6
}

每個欄位的顯示數量

func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
switch component {
case 0: return numbersOf24.count //小時
case 2: return numbersOf60.count //分鐘
case 4: return numbersOf60.count //秒數
default: return 1 //label
}
}

每個欄位的顯示內容

func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
switch component {
case 0: return numbersOf24[row].description
case 1:
let hour = UILabel()
hour.text = "hours"
timePicker.setPickerLabelSize(labels: [1 : hour])
return ""
case 2: return numbersOf60[row].description
case 3:
let min = UILabel()
min.text = "min"
timePicker.setPickerLabelSize(labels: [3 : min])
return ""
case 4: return numbersOf60[row].description
case 5:
let sec = UILabel()
sec.text = "sec"
timePicker.setPickerLabelSize(labels: [5 : sec])
return ""
default: return "error"
}
}

選擇列後直接儲存數字。

func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
switch component {
case 0: hour = numbersOf24[row]
case 2: minute = numbersOf60[row]
case 4: second = numbersOf60[row]
default: return
}
}
}

計時

時間處理

將從time picker獲取到的時間即時換算成時間差

var timeInterval: Int {
hour * 3600 + minute * 60 + second
}

設定結束時間的字串顯示

func setEndTime() -> String {
let endTimeInterval: TimeInterval = TimeInterval(timeInterval) + Date().timeIntervalSince1970
endTime = Date(timeIntervalSince1970: endTimeInterval)
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.dateFormat = "HH:mm"
return formatter.string(from: endTime)
}

開始計時

顯示剩餘的倒數時間,倒數到0之後會結束倒數。

func start() {
countdownInterval = isReset ? Double(timeInterval) : Double(resumeInterval)
isReset = false
isRunning = true

endTimeLabel.text = setEndTime()


let startAngle: CGFloat = 270
let perAngle = CGFloat(360 / timeInterval)
var currentAngle: CGFloat = 360
timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true, block: { [self](_) in
let countdownStr:String = {
let hour = (Int(countdownInterval) / 3600).formatted(.number.precision(.integerLength(2)))
let minute = ((Int(countdownInterval) % 3600) / 60).formatted(.number.precision(.integerLength(2)))
let second = (Int(countdownInterval) % 60).formatted(.number.precision(.integerLength(2)))
return Int(hour) == 0 ? "\(minute):\(second)" : "\(hour):\(minute):\(second)"
}()
digitalTimerLabel.text = countdownStr

顯示剩餘時間的progress bar

currentAngle = startAngle + CGFloat(countdownInterval) * perAngle
let path = UIBezierPath()
path.addArc(withCenter: center, radius: radius, startAngle: startAngle.degree, endAngle: currentAngle.degree, clockwise: true)
progressBar.path = path.cgPath

countdownInterval -= 0.01

if countdownInterval < 0 {
createNotificationNow()
cancel()
}
})
timer.fire()
}

--

--