
Timerクラスを使用した、簡易的なストップウォッチを作成する。
ラップ機能は実装していないが、Start/Stop/Resetボタンにより1/100秒単位での時間の計測を行うことができる。
Contents
今回作るもの
実装する機能は以下の通りである。
- 時間を1/100秒単位で計測する
- Stopボタンを押して再度Startボタンを押した場合、続きの時間から計測する
- 表示は分・秒・コンマ秒まで
- ラップ機能は搭載しない
- ストップウォッチの作動状態により、ボタンの活性・非活性を切り替える
実行イメージはこのような感じ。
変数の準備・アウトレット接続
使用するUI部品は、タイムを表示するラベル・Start/Stop/Resetボタン。それぞれをアウトレット接続しておく。
また、タイマー処理に関わる変数を宣言しておく。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
class ViewController: UIViewController { var timer = Timer() // Timerクラス var startTime: TimeInterval = 0 // Startボタンを押した時刻 var elapsedTime: Double = 0.0 // Stopボタンを押した時点で経過していた時間 var time : Double = 0.0 // ラベルに表示する時間 @IBOutlet weak var labelTimer: UILabel! // タイムを表示するラベル @IBOutlet weak var buttonStart: UIButton! // Startボタン @IBOutlet weak var buttonStop: UIButton! // Stopボタン @IBOutlet weak var buttonReset: UIButton! // Resetボタン // 以下略 } |
ボタンを押した時の動作
ボタンの活性・非活性の切り替わり方
Start/Stop/Resetボタンは、ストップウォッチが作動しているかしていないかで活性/非活性を切り替える。
- ストップウォッチ作動中は、Stopボタンを活性、Start/Resetボタンを非活性にする
- ストップウォッチ停止中は、Start/Resetボタンを活性、Stopボタンを非活性にする
経過時間の計算方法
ただ単にラベルに表示するタイムを「Startボタンが押されてから経過した時間」とすると、一度Stopボタンを押してから再度Startボタンを押した時に正しいタイムが表示されない。
この問題に対処するため、タイムの測定は以下のような処理で実現する。
- Startボタンが押されたら、Startボタンを押した時間を記録し、現在の時刻との差をラベルに表示する
- Stopボタンが押されたら、タイマー処理を中断し、これまでに経過した時間を変数に保持しておく
- Resetボタンが押されず再度Startボタンが押された場合、Startボタンを押した時間を上書きし、現在の時刻との差に変数に保持した経過時間を足した時間をラベルに表示する
- 変数に保持した経過時間は、Resetボタンが押されたら0に戻す
Startボタンの処理
各種ボタンの活性・非活性を切り替え、タイマー処理を登録する。
1 2 3 4 5 6 7 8 9 10 11 12 |
// Startボタンを押した時の処理 @IBAction func tapStart() { // Startボタン、Resetボタンを無効化 buttonStart.isEnabled = false buttonReset.isEnabled = false // Stopボタンを有効化 buttonStop.isEnabled = true // Startボタンを押した時刻を保存 startTime = Date().timeIntervalSince1970 // 0.01秒おきに関数「update」を呼び出す timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.update), userInfo: nil, repeats: true) } |
タイマー処理で呼び出される処理は以下のように実装する。
1 2 3 4 5 6 7 8 9 10 11 12 |
// 0.01秒ごとに呼び出される処理 func update() { // 経過時間は以下の式で計算する // (現在の時刻 - Startボタンを押した時刻) + Stopボタンを押した時点で経過していた時刻 time = Date().timeIntervalSince1970 - startTime + elapsedTime // 時間を小数点前後で分割(小数点以下は2桁だけ取得) let sec = Int(time) let msec = Int((time - Double(sec)) * 100) // 「XX:XX.XX」形式でラベルに表示する let displayStr = NSString(format: "%02d:%02d.%02d", sec/60, sec%60, msec) as String labelTimer.text = displayStr } |
ちなみに筆者は当初「0.01秒ごとにラベルを0.01秒ずつ増やしていけばいいんでないの?」と思っていたが、残念ながら処理の重さのために0.01秒ごとに処理を行うように指定しても実際はもっと長い間隔で処理が行われる。
そのため上記の処理のように「処理が呼び出されるたびに、その時点で経過している時間を表示」としないと、とてもポンコツなストップウォッチが完成してしまう。(見てて面白いので一度試してみてほしい)
Stopボタンの処理
Stopボタンが押されたら、ボタンの活性・非活性の切り替え、タイマー処理の停止、経過時間の保存を行う。経過時間を保存することにより、再びStartボタンが押されても継続して時間を計測することができるようになる。
1 2 3 4 5 6 7 8 9 10 11 12 |
// Stopボタンを押した時の処理 @IBAction func tapStop() { // Startボタン、Resetボタンを有効化 buttonStart.isEnabled = true buttonReset.isEnabled = true // Stopボタンを無効化 buttonStop.isEnabled = false // タイマー処理を止める timer.invalidate() // 再度Startボタンを押した時に加算するため、これまでに計測した経過時間を保存 elapsedTime = time } |
Resetボタンの処理
経過時間のリセット、ラベルの初期化を行うだけである。
1 2 3 4 5 6 |
// Resetボタンを押した時の処理 @IBAction func tapReset() { // 経過時間、ラベルを初期化する elapsedTime = 0.0 labelTimer.text = "00:00.00" } |
完成したソース
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
import UIKit class ViewController: UIViewController { var timer = Timer() // Timerクラス var startTime: TimeInterval = 0 // Startボタンを押した時刻 var elapsedTime: Double = 0.0 // Stopボタンを押した時点で経過していた時間 var time : Double = 0.0 // ラベルに表示する時間 @IBOutlet weak var labelTimer: UILabel! // タイムを表示するラベル @IBOutlet weak var buttonStart: UIButton! // Startボタン @IBOutlet weak var buttonStop: UIButton! // Stopボタン @IBOutlet weak var buttonReset: UIButton! // Resetボタン // Startボタンを押した時の処理 @IBAction func tapStart() { // Startボタン、Resetボタンを無効化 buttonStart.isEnabled = false buttonReset.isEnabled = false // Stopボタンを有効化 buttonStop.isEnabled = true // Startボタンを押した時刻を保存 startTime = Date().timeIntervalSince1970 // 0.01秒おきに関数「update」を呼び出す timer = Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(self.update), userInfo: nil, repeats: true) } // 0.01秒ごとに呼び出される処理 func update() { // 経過時間は以下の式で計算する // (現在の時刻 - Startボタンを押した時刻) + Stopボタンを押した時点で経過していた時刻 time = Date().timeIntervalSince1970 - startTime + elapsedTime // 時間を小数点前後で分割(小数点以下は2桁だけ取得) let sec = Int(time) let msec = Int((time - Double(sec)) * 100) // 「XX:XX.XX」形式でラベルに表示する let displayStr = NSString(format: "%02d:%02d.%02d", sec/60, sec%60, msec) as String labelTimer.text = displayStr } // Stopボタンを押した時の処理 @IBAction func tapStop() { // Startボタン、Resetボタンを有効化 buttonStart.isEnabled = true buttonReset.isEnabled = true // Stopボタンを無効化 buttonStop.isEnabled = false // タイマー処理を止める timer.invalidate() // 再度Startボタンを押した時に加算するため、これまでに計測した経過時間を保存 elapsedTime = time } // Resetボタンを押した時の処理 @IBAction func tapReset() { // 経過時間、ラベルを初期化する elapsedTime = 0.0 labelTimer.text = "00:00.00" } override func viewDidLoad() { super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } override func didReceiveMemoryWarning() { super.didReceiveMemoryWarning() // Dispose of any resources that can be recreated. } } |