【Swift】ストップウォッチアプリをNSDateで作ってみた

f:id:clrmemory:20170422183617p:plain 

今回はCocoa Programmingでストップウォッチを作成する方法を紹介します。

Mac OSXアプリでストップウォッチを作成するためには、ScheduledTimerやNSDateなどのコードを使用します。

 

これらのコードは、先日記事にした時計アプリでも出てきたコードなので参考にしてみてください。ではまずはストップウォッチアプリの完成系を確認していきましょう。

 

 

はじめに

 

今回作成するストップウォッチアプリは以下のようなレイアウトにしてみました。

 

 

 

Startボタンを押すとカウントが開始し、Stopボタンで停止、Resetボタンで数値を0に戻すようになっています。このようなシンプルなストップウォッチであれば簡単に作成できました。

動画も撮影しましたので、以下から確認しておくと実際の動作をイメージしやすいかと思います。

また、先日記事にした時計アプリと似たような方法で作成することができるので、以下のリンクから確認してみてください。

 

https://clrmemory.com/swift/cocoa-clock-app/

 

では、まずはストーリーボードから作成していきましょう。

 

ストーリーボードを作成

 

先ほど紹介したようなレイアウトを作成します。

以下を参考にして、「Custom View」 「Label」 「Button」をそれぞれ配置してください。

 

 

 

 

今回はボタンに「Gradient Button」を使ってみました。ストーリーボードに配置するオブジェクトはたったこれだけです。

 

 

続いて、このままViewController.swiftへのアウトレット (アクション) 接続を済ませてしまいましょう。アウトレット接続というのは、オブジェクトで右クリックしながらドラッグ、もしくはcontrolを押しながらドラッグするあれですね。

今回のストップウォッチアプリでアウトレット接続が必要なオブジェクトは、LabelとButtonです。以下を参考にしてアウトレット接続を完了させてください。

 

   //Label
    @IBOutlet weak var timerLabel: NSTextField!

    override func viewDidLoad() {...}
    override var representedObject: Any? {...}

    //Start
    @IBAction func startButton(_ sender: Any) {
    }

    //Stop
    @IBAction func stopButton(_ sender: Any) {
    }

    //Reset
    @IBAction func resetButton(_ sender: Any) {
    }

 

こんな感じになったと思います。ボタンはそれぞれアクションで接続されているので注意してください。

 

これらの中身は今はまだ空の状態ですが、実際のコードを記述するときに埋めていきます。

では実際にコードを書いていきましょう。

 

コードの流れ

 

今回は以下のような手順で、ストップウォッチアプリを作成します。

 

 

1 変数を定義

2 スタートボタンの処理

3 繰り返し処理

4 経過時間を取得

5 ラベルを更新

6 現在の状態を切り替え

7 ストップボタンの処理

8 リセットボタンの処理

 

 

では、まずは変数を定義します。

 

変数を定義

 

先ほどアウトレット接続した「timerLabel」の下あたりに、以下のような記述をしましょう。

 

var nowTime: Double = Double()
var elapsedTime: Double = Double()
var displayTime: Double = Double()
var savedTime: Double = Double()

var startOrStop: Bool = false
var timer: Timer = Timer()

 

 

nowTime -> 現在時刻

elapsedTime -> 経過時間

displayTime -> 実際に表示する時間

savedTime -> ストップ時に保存しておく時間

 

startOrStop -> ストップウォッチの状態

timer -> タイマー

 

 

今回はこのように作成してみました。自分でわかりやすいように変更する場合は、後に出てきた時、同じ名前に変更してくださいね。

 

ではこれらに値を持たせていきましょう。

 

Startボタンが押された時の処理

 

まずはスタートボタンが押された時の処理を記述していきます。

先ほどアクション接続した「startButton( ) { }」の中に以下のような記述を追加しましょう。

 

@IBAction func startButton(_ sender: Any) {
    if !startOrStop{
        nowTime = NSDate.timeIntervalSinceReferenceDate
        timer = Timer.scheduledTimer(timeInterval: 1/100, target: self, selector: #selector(stopWatch), userInfo: nil, repeats: true)

        toggleStartOrStop()
    }
}

 

 

まずは「startOrStop」で、現在ストップウォッチが作動しているのかを判定しています。作動中は「true」、停止中は「false」です。

この値は、後述する「toggleStartOrStop( )」で切り替えるので、とりあえず忘れてください。

 

nowTime

 

 

先ほどnowTimeは「現在時刻」だと説明しましたが、厳密には「2001年1月1日00:00.00」からの経過時間になります。

 

ですので、このままprint( )などで出力しても、現在時刻は取得できません。そこで、今回紹介する方法では、nowTimeからの経過時間をカウントさせます。

 

繰り返し処理

 

timer = Timer.scheduledTimer( )では「繰り返し」の処理を記述しています。

今回の例では「1/100秒」でセレクタ「stopWatch」を呼び出し、それを「repeat」するというような内容になっています。

 

 

さて、ここまでで新しく「stopWatch( )」と「toggleStartOrStop( )」が登場しました。これらを先に記述してしまいましょう。

記述する場所は、resetButton( ){ } の下にしておくとわかりやすいかと思います。

 

stopWatch( )

 

stopWatch( )では、nowTimeに格納した時刻からの経過時間を設定します。

 

func stopWatch(){
    elapsedTime = NSDate.timeIntervalSinceReferenceDate
    displayTime = (elapsedTime + savedTime) - nowTime

    reloadText()
}

 

elapsedTime = NSDate.timeIntervalSinceReferenceDateでnowTimeと同じ値を取得できました。

 

 

ですが注意していただきたいのが、このstopWatch( )は先ほどスタートボタンの中でリピート処理を記述したという点です。

繰り返して呼ばれるので、elapsedTimeの値は1/100秒ごとに増え続けるというわけですね。

 

これで「elapsedTime」から「nowTime」を引くことで「経過時間」が取得できました。

savedTimeは、ストップボタンでカウントを中断してから再度スタートした場合の経過時間を取得するものです。これについては後ほど。

 

 

 

さてここで、reloadText( )が登場しました。

名前の通りこちらは「テキストを更新」するものになっています。値が変わってもストップウォッチの表示が変わらなければ意味がありませんからね。

 

テキストを更新

 

func reloadText(){
    let min = Int(displayTime / 60)
    let sec = Int(floor(displayTime)) % 60
    let decimal = Int((displayTime - Double(floor(displayTime))) * 100)

    let minText = String(format: "%02d", min)
    let secText = String(format: "%02d", sec)
    let decimalText = String(format: "%02d", decimal)

    timerLabel.stringValue = "\(minText):\(secText).\(decimalText)"
}

 

 

ここでは「分」「秒」「少数」の値から実際に表示する時間を設定し、それに合わせてtimerLabelのテキストを変更するものになります。

 

displayTime -> elapsedTime + savedTimeからnowTimeを引いた時間

 

 

min

-> displayTime / 60にすることで、経過時間から「分」の値を取得できます。これは問題ないと思います。60秒 - 60 = 1分。

 

 

sec

-> minと同様にこちらは「秒」を取得します。経過時間を60で割った時の余りを計算することで「秒」になります。「1や59」は「0余り1や59」なので、数値はそのまま。60の時だけ余りが出ないので0が入る。

 

 

decimal

-> 時間の少数を取得します。

まずDouble(floor(displayTime)) に着目してください。こちらはdisplayTimeの少数を「floor」で切り上げつつ、Double型に変換してあります。

12.2354秒だった場合は「12」

 

 

これを実際の経過時間から引くことで、経過時間の少数部分だけを取得できます。

12.2354 - 12 = 「0.2354」

ですが、これだけだと「0.」が邪魔なので、*100で整数に変換してあります。

 

続いて、「minText」「secText」「decimalText」をまとめて説明します。

こちらの記事でも紹介しているのですが、minやsecの桁数によって先頭に0をつけるかどうかの処理が追加されています。

 

https://clrmemory.com/swift/add-0-text/

 

 

最終的に「timerLabel」のテキストをstringValueで指定しています。

 

timerLabel.stringValue = "\(minText):\(secText).\(decimalText)"

 

「:」や「.」は普通の文字列です。

 

 

ここまでで1度実行してみましょう。

以下のように数値をカウントするアプリケーションが起動したら成功です。

 

 

 

現在の状態を切り替え

 

ストップウォッチが作動中なのか、停止しているのかを判断するために「startOrStop」を作成しましたよね。

この値をtoggleStartOrStop( )で切り替えるようにしましょう。

 

func toggleStartOrStop(){
    if startOrStop {
        startOrStop = false
    }else{
        startOrStop = true
    }
}

 

startOrStopがtrueだったら「false」に変更し、falseだったら「true」に変更するだけの簡単な処理なので問題ないでしょう。

ボタンが押された時にtoggleStartOrStopが呼び出されます。

 

 

これで、現在の状態によって処理を変更できるようになりました。試しに実行してからスタートボタンを連打してみてください。

 

 

連打しても何も起こらなかったと思います。

 

ストップボタンが押された時の処理

 

続いてストップボタンが押された時の処理「stopButton( ){ }」を記述していきましょう。

stopButtonでは、主にリピートの停止を行っています。

 

@IBAction func stopButton(_ sender: Any) {

    if startOrStop{
        timer.invalidate()
        savedTime = displayTime

        toggleStartOrStop()
    }
}

 

timer.invalidate( )で起動中のtimerのリピートをストップさせることができます。

 

あとは、再度スタートした時の為に、現在までの経過時間をsavedTimeに保存しています。

savedTimeはstopWatch( )内ですでに記述してあるので確認してください。

 

 

ストップボタンでも同様にtoggleStartOrStopを呼び出しましょう。

 

ここで1度確認してみてください。

画像では伝わりませんが、ストップウォッチのカウントが停止しています。

 

 

リセットボタンが押された時の処理

 

最後にリセットボタンが押された時の処理を記述しましょう。

今回作成したストップウォッチでは「ストップウォッチが止まっているならリセットできる」ようにしてみました。

 

@IBAction func resetButton(_ sender: Any) {
    if !startOrStop{
        savedTime = 0.0
        displayTime = 0.0

        reloadText()
    }
}

 

まずはstartOrStopの状態を判定して処理を行います。resetButtonでは特に難しい記述はなく、savedTime(保存時間)とdisplayTime(経過時間)を0に戻しているだけです。

 

 

数値が0に戻った後はテキストを更新しなければならないので、reloadText( )でテキストを「00:00.00」に戻しています。

 

最後にリセット処理を確認しましょう。

 

 

先ほどのカウントが0に戻りました。

 

まとめ

 

今回紹介した方法でストップウォッチのMacアプリが完成したと思います。

scheduledTimerやNSDate.timeIntervalSinceReferenceDateなどのコードさえわかってしまえば簡単に作成できるのでぜひ試してみてください。

 

ではまた。

新着記事