swiftでiPhone標準の時計アプリを作ろう 完成コード タイマー

2019年9月6日金曜日 更新:2019年09月17日

swift 時計

t f B! P L
Xcode 10.3
swift5
ios 12.4

完成品

機能面を重視したのでデザインは特に意識していません
また、アラーム音もデフォルトしか設定していないので今回のサウンド設定は飾りです。
(ご要望があれば追加します。)


UI

実装の仕方はいろいろありますが、
今回はTableViewの上に独自Cellを乗せる設計にしました。


コード

AppDelegate

  1. import UIKit
  2. import UserNotifications //UserNotificationsをimport
  3. @UIApplicationMain
  4. class AppDelegate: UIResponder, UIApplicationDelegate {
  5.  
  6. var window: UIWindow?
  7.  
  8. var backgroundTaskID: UIBackgroundTaskIdentifier = UIBackgroundTaskIdentifier(rawValue: 0)
  9.  
  10. func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
  11. // 通知許可の取得
  12. UNUserNotificationCenter.current().requestAuthorization(
  13. options: [.alert, .sound, .badge]){
  14. (granted, _) in
  15. if granted{
  16. UNUserNotificationCenter.current().delegate = self
  17. }
  18. }
  19. return true
  20. }
  21.  
  22. //バックグラウンド遷移移行直前に呼ばれる
  23. func applicationWillResignActive(_ application: UIApplication) {
  24. // 新しいタスクを登録
  25. backgroundTaskID = application.beginBackgroundTask {
  26. [weak self] in
  27. application.endBackgroundTask((self?.backgroundTaskID)!)
  28. self?.backgroundTaskID = UIBackgroundTaskIdentifier.invalid
  29. }
  30. }
  31. //アプリがアクティブになる度に呼ばれる
  32. func applicationDidBecomeActive(_ application: UIApplication) {
  33. //タスクの解除
  34. application.endBackgroundTask(self.backgroundTaskID)
  35. }
  36. }
  37.  
  38. extension AppDelegate: UNUserNotificationCenterDelegate{
  39. func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
  40. // アプリ起動中でもアラートと音で通知
  41. completionHandler([.alert, .sound])
  42. }
  43. func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
  44. // バックグラウンドで来た通知をタップしてアプリ起動したら呼ばれる
  45.       completionHandler()
  46. }
  47. }
  48.  
  49.  

TimerVC

  1. import UIKit
  2. import UserNotifications //UserNotificationsをimport
  3. class TimerVC: UIViewController, UITableViewDelegate,UITableViewDataSource {
  4. @IBOutlet weak var tableView: UITableView!
  5. var timerOn:Bool = false
  6. var timerStart:Bool = false
  7. var time:Double = 0
  8. override func viewDidLoad() {
  9. super.viewDidLoad()
  10. //cellを登録
  11. registerCell(cellName: "TimerLabelCell")
  12. registerCell(cellName: "TimerSetCell")
  13. registerCell(cellName: "SoundSetCell")
  14. registerCell(cellName: "TimerButtonCell")
  15. }
  16. //cellを登録
  17. func registerCell(cellName:String){
  18. tableView.register(UINib(nibName: cellName, bundle: nil), forCellReuseIdentifier: cellName)
  19. }
  20. //セクションごとのセルの個数を指定
  21. func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
  22. return 1
  23. }
  24. //セクション数
  25. func numberOfSections(in tableView: UITableView) -> Int {
  26. return 3
  27. }
  28.     //セクションごとにセルの高さを設定
  29. func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
  30. switch indexPath.section {
  31. case 0:
  32. return 220
  33. case 1:
  34. return 44
  35. case 2:
  36. return 160
  37. default:
  38. return 0
  39. }
  40. }
  41. // セクションごとにセル生成
  42. func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  43. switch indexPath.section {
  44. case 0:
  45. //タイマーがスタートしているかどうか
  46. if timerStart {
  47.                // タイマーがスタートしていた時TimerLabelCellを生成
  48. let cell = tableView.dequeueReusableCell(withIdentifier: "TimerLabelCell") as! TimerLabelCell
  49. cell.delegate = self
  50.                // タイマー動いているか
  51. if timerOn {
  52. cell.time = time
  53. cell.startTimer()
  54. }else{
  55. cell.stopTimer()
  56. }
  57. return cell
  58. }else{
  59.                // タイマーがスタートしていない時TimerSetCellを生成
  60. let cell = tableView.dequeueReusableCell(withIdentifier: "TimerSetCell") as! TimerSetCell
  61. cell.delegate = self
  62. cell.getTimer()
  63. return cell
  64. }
  65. case 1:
  66. let cell = tableView.dequeueReusableCell(withIdentifier: "SoundSetCell") as! SoundSetCell
  67. return cell
  68. case 2:
  69. let cell = tableView.dequeueReusableCell(withIdentifier: "TimerButtonCell") as! TimerButtonCell
  70. cell.delegate = self
  71. if !timerStart {
  72. cell.start()
  73. }
  74. return cell
  75. default:
  76. return UITableViewCell()
  77. }
  78. }
  79. }
  80.  
  81. extension TimerVC:TimerLabelCellDelegate {
  82. func timerLabelCell(stopTimer: TimerLabelCell, remainingTime: Double) {
  83. time = remainingTime
  84. }
  85. func timerLabelCell(timerEnd alarmSet: TimerLabelCell) {
  86. timerStart = false
  87. timerOn = false
  88. tableView.reloadData()
  89. }
  90. }
  91.  
  92. extension TimerVC:TimerSetCellDelegate {
  93. func timerSetCell(setTime: TimerSetCell, time: Double) {
  94. self.time = time
  95. }
  96. }
  97.  
  98. extension TimerVC:TimerButtonCellDelegate {
  99. func timerButtonCell(startStopTime: TimerButtonCell) {
  100. //通知を設定
  101. let center = UNUserNotificationCenter.current()
  102. center.removePendingNotificationRequests(withIdentifiers: ["Timer"])
  103. if time != 0 {
  104. timerStart = true
  105. if timerOn {
  106. timerOn = false
  107. startStopTime.resume()
  108. }else{
  109. timerOn = true
  110. startStopTime.pause()
  111. let content = UNMutableNotificationContent()
  112. content.sound = UNNotificationSound.default
  113. content.title = "Timer Done"
  114. let trigger = UNTimeIntervalNotificationTrigger(timeInterval: TimeInterval(time), repeats: false)
  115. let identifier = "Timer"
  116. let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
  117. UNUserNotificationCenter.current().add(request){ (error : Error?) in
  118. if let error = error {
  119. print(error.localizedDescription)
  120. }
  121. }
  122. }
  123. }else{
  124. timerStart = false
  125. startStopTime.start()
  126. }
  127. tableView.reloadData()
  128. }
  129. func timerButtonCell(cancelTime: TimerButtonCell) {
  130. timerStart = false
  131. timerOn = false
  132. cancelTime.start()
  133. let center = UNUserNotificationCenter.current()
  134. center.removePendingNotificationRequests(withIdentifiers: ["Timer"])
  135. tableView.reloadData()
  136. }
  137. }

TimerLabelCell

  1. import UIKit
  2.  
  3. protocol TimerLabelCellDelegate {
  4. func timerLabelCell(timerEnd:TimerLabelCell)
  5. func timerLabelCell(stopTimer:TimerLabelCell,remainingTime:Double)
  6. }
  7.  
  8. class TimerLabelCell: UITableViewCell {
  9.  
  10. @IBOutlet weak var timerLabel: UILabel!
  11. var timer = Timer()
  12. var time:Double = 0 {
  13. didSet {
  14. //タイマーをtimerLabelにセット
  15. timerLabel.text = timeString(time: time)
  16. }
  17. }
  18. var delegate:TimerLabelCellDelegate!
  19. //タイマースタート
  20. func startTimer(){
  21. //タイマーが動いているか
  22. if timer.isValid {
  23. timer.invalidate()
  24. }
  25. // タイマー生成、開始 1秒後の実行
  26. timer = Timer.scheduledTimer(
  27. timeInterval: 1.0, // 時間間隔
  28. target: self, // タイマーの実際の処理の場所
  29. selector: #selector(TimerLabelCell.tickTimer(_:)), // メソッド タイマーの実際の処理
  30. userInfo: nil,
  31. repeats: true)
  32. }
  33. //タイマー停止
  34. func stopTimer(){
  35. timer.invalidate()
  36. delegate.timerLabelCell(stopTimer: self, remainingTime: time)
  37. }
  38. // タイマー処理
  39. @objc func tickTimer(_ timer: Timer) {
  40. time -= 1
  41. timerLabel.text = timeString(time: time)
  42. //timeが-1になった時
  43. if time == -1 {
  44. // タイマーの停止
  45. timer.invalidate()
  46. delegate.timerLabelCell(timerEnd: self)
  47. }
  48. }
  49. //時分秒を取得
  50. func timeString(time:TimeInterval) -> String {
  51. let hours = Int(time) / 3600 //時
  52. let minutes = Int(time) / 60 % 60 //分
  53. let seconds = Int(time) % 60 //秒
  54. return String(format:"%02i:%02i:%02i", hours, minutes, seconds)
  55. }
  56. }

TimerSetCell

  1. import UIKit
  2.  
  3. protocol TimerSetCellDelegate {
  4. func timerSetCell(setTime: TimerSetCell, time: Double)
  5. }
  6.  
  7. class TimerSetCell: UITableViewCell,UIPickerViewDataSource, UIPickerViewDelegate {
  8. @IBOutlet weak var pickerView: UIPickerView!
  9. var delegate:TimerSetCellDelegate!
  10. var time:Double = 0 
  11. var hour:Double = 0 //時
  12. var minutes:Double = 0 //分
  13. var seconds:Double = 0  //秒
  14. let labelText = ["hours", "min", "sec"]
  15. var labels :[UILabel] = []
  16. override func awakeFromNib() {
  17. super.awakeFromNib()
  18. pickerView.delegate = self
  19. pickerView.dataSource = self
  20. if time != 0 {
  21. delegate.timerSetCell(setTime: self, time: time)
  22. }
  23. }
  24. public override func layoutSubviews() {
  25. super.layoutSubviews()
  26. self.reloadAllComponentLabels()
  27. }
  28. //ピッカーに表示する列数
  29. func numberOfComponents(in pickerView: UIPickerView) -> Int {
  30. return 3
  31. }
  32. //ピッカーに表示する値(数字)
  33. func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
  34. switch component {
  35. case 0:
  36. return 24
  37. case 1,2:
  38. return 60
  39. default:
  40. return 0
  41. }
  42. }
  43. func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
  44. switch component {
  45.        //秒に変換
  46. case 0:
  47. hour = Double(row * 3600)
  48. case 1:
  49. minutes = Double(row * 60)
  50. case 2:
  51. seconds = Double(row)
  52. default:
  53. break;
  54. }
  55. time = hour + minutes + seconds
  56. delegate.timerSetCell(setTime: self, time: time)
  57. }
  58. func timeString(time:TimeInterval) -> String {
  59. let hours = Int(time) / 3600
  60. let minutes = Int(time) / 60 % 60
  61. let seconds = Int(time) % 60
  62. return String(format:"%i:%02i:%02i", hours, minutes, seconds)
  63. }
  64. func getTimer(){
  65. delegate.timerSetCell(setTime: self, time: time)
  66. }
  67. //ピッカーに表示する値(文字列)
  68. func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
  69. return row.description
  70. }
  71. //列の幅
  72. func pickerView(_ pickerView: UIPickerView, widthForComponent component: Int) -> CGFloat {
  73. return pickerView.frame.size.width/4
  74. }
  75.   // ラベルの配置
  76. public func reloadAllComponentLabels() {
  77. let fontSize = UIFont.systemFontSize
  78. let labelTop = self.bounds.origin.y + self.bounds.height / 2 - fontSize
  79. let labelHeight = self.pickerView.rowSize(forComponent: 0).height
  80. var labelOffset = self.bounds.origin.x // Componentの右端
  81. for i in 0...(self.numberOfComponents(in: pickerView)) - 1 {
  82. if self.labels.count == i {
  83. let label = UILabel()
  84. label.text = labelText[i]
  85. label.backgroundColor = UIColor.clear
  86. label.font = UIFont.boldSystemFont(ofSize: fontSize)
  87. label.sizeToFit()
  88. self.addSubview(label)
  89. self.labels.append(label)
  90. }
  91. // ラベルの位置を決める
  92. let labelWidth = self.labels[i].frame.width
  93. labelOffset += pickerView.rowSize(forComponent: i).width
  94. self.labels[i].frame = CGRect(x: labelOffset - labelWidth + pickerView.rowSize(forComponent: i).width/2, y: labelTop, width: labelWidth, height: labelHeight)
  95. }
  96. }
  97. }

SoundSetCell

  1. import UIKit
  2.  
  3. class SoundSetCell: UITableViewCell {
  4.  
  5. @IBOutlet weak var soundNameLabel: UILabel!
  6.  
  7. }

TimerButtonCell

  1. import UIKit
  2.  
  3. protocol TimerButtonCellDelegate {
  4. func timerButtonCell(startStopTime : TimerButtonCell)
  5. func timerButtonCell(cancelTime : TimerButtonCell)
  6. }
  7.  
  8. class TimerButtonCell: UITableViewCell {
  9.  
  10. @IBOutlet weak var startButton: UIButton!
  11. @IBOutlet weak var cancelButton: UIButton!
  12. var delegate:TimerButtonCellDelegate!
  13. override func awakeFromNib() {
  14. super.awakeFromNib()
  15. cancelButton.isEnabled = true
  16. }
  17.  
  18. override func setSelected(_ selected: Bool, animated: Bool) {
  19. super.setSelected(selected, animated: animated)
  20. }
  21.     //開始停止
  22. @IBAction func startstop(_ sender: Any) {
  23. delegate.timerButtonCell(startStopTime: self)
  24. }
  25. //キャンセル
  26. @IBAction func cancel(_ sender: Any) {
  27. delegate.timerButtonCell(cancelTime: self)
  28. cancelButton.isEnabled = true
  29. }
  30. //スタートボタンに変更
  31. func start(){
  32. startButton.setTitle("Start", for: .normal)
  33. startButton.backgroundColor = #colorLiteral(red: 0.1799043417, green: 0.6762347817, blue: 0.2553646266, alpha: 1)
  34. }
  35.    //一時停止に変更
  36. func pause(){
  37. startButton.setTitle("Pause", for: .normal)
  38. startButton.backgroundColor = .orange
  39.  
  40. }
  41.     //再開に変更
  42. func resume(){
  43. startButton.setTitle("Resume", for: .normal)
  44. startButton.backgroundColor = #colorLiteral(red: 0.1799043417, green: 0.6762347817, blue: 0.2553646266, alpha: 1)
  45.  
  46. }
  47. }
githubはこちら

参考

QooQ