サイト名変更・お引越しのお知らせ

【iOSアプリチュートリアル】ストップウォッチアプリを作ろう

ストップウォッチアプリ

iOSアプリ開発のUIのチュートリアルとして、ストップウォッチアプリを一緒に作っていきましょう。

下記の記事で学習するスキルを中心にチュートリアルにしています。

学習できること
  • ストーリーボードを使ったUI構築
  • ButtonやLabelなどのUI部品の使い方
  • UIとロジックの連携

環境・バージョン
  • swift 5.2.4
  • Xcode 11.6
  • macOS 10.15.6 Catalina
  • iOS 14.0.1

完成形

まずは完成系のアプリを確認し、イメージを掴みましょう

ストップウォッチ完成形

スタート」と「ストップ」ボタンがあり、

スタートボタンを押すとラベルに時間が加算されていき

ストップを押すとその時点での時間が表示されるアプリです。

プロジェクト作成

それでは今回のプロジェクトを作成していきましょう。

まずはSingle View Appを選んでください。

Xcode Init

アプリ名はお好きなもので良いですが、ここではStopWatchとしました。

xcodeアプリ名設定

UI作成

今回のアプリではストップウォッチをスタート・ストップさせるUIButton
経過時間を表示するUILabelが必要です。

UILabelの配置

まずは経過時間を表示するUILabelを配置していきましょう。

Main.storyboardを開いて画面上部にUILabelを配置します。

ラベルの配置

初期値は「00:00:00」と表示したいのでUILabelのテキストを変更しましょう。

UILabelのテキストを変更

UIButtonの配置

スタート・ストップさせるUIButtonを配置します。

場所はどこでも問題ないですが、今回はUILabelの下あたりに配置しました。

ボタンの配置

さらにボタンのテキストをそれぞれ「スタート」「ストップ」に変更しましょう。

ボタンのテキストの変更

シンプルですが、これでUI部分に関しては完成です。

一度この段階でビルドしておきましょう。

アプリをビルドする

このような画面が表示されれば成功です。

ViewControllerへの紐付け

UI実装の最後にUILabelとUIButtonアクションをViewControllerに定義していきます。

まずはUILabelです。

Ctrl+ドラッグをすると青い線が出るのでViewControllerまで引っ張っていきましょう。

そうすると入力・選択ができるポップアップが表示されます。

今回はNameのみ「timerLabel」と変更しましょう。

最終的に以下の画像のようになっていたらConnectを押しましょう。

Labelとの紐付け
紐付け名の変更

次はUIButtonです。

こちらも先ほどと同様にCtrl+ドラッグをしながら青い線をViewControllerまで引っ張っていきましょう。

UILabelと違いUIButtonはアクションを定義するのでConnectionを「Action」に変更します。

ボタンの紐付け
ボタン紐付け名の変更

次にNameですが、スタートボタンは「tappedStart」、ストップボタンは「tappedStop」と変更します。

最終的に以下の画像のようになっていたらConnectを押しましょう。

ラベルの変更

紐付けが完了すると、ViewControllerはこのようになっていると思います。

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var timerLabel: UILabel!
    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.
    }

    @IBAction func tappedStart(_ sender: Any) {
    }

    @IBAction func tappedStop(_ sender: Any) {
    }
}

これでUIが完成しました。

ストップウォッチ機能

次はストップウォッチの機能を作っていきましょう。

まずは要件の確認ですがスタートボタンを押すとタイマーが起動し、時間が経過するごとに時間が表示するです。

これを満たすためには次のような流れが必要ですね。

  1. 「スタート」ボタンを押した時にタイマーが起動する
  2. 時間が経過するごとにその時の時間が表示される
  3. ストップボタンを押すと時間が止まる
  4. 再びスタートボタンを押した時に0秒からタイマーを計測する

それでは実装していきましょう。

タイマーを起動させる

タイマーアプリなので時間の経過を測定する必要があります。

時間を記録しておく変数を用意しておき、一定時間ごとにその変数に値を足していく方針でいきましょう。

まずは経過時間の変数を用意しておきます。

var elapsedTime: Float = 0.0

次に 一定時間ごと を実現するために今回は Timer クラスを使いましょう。

Timer には scheduledTimer() というクラスメソッドがあるのでこれを使います。

Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
    // 0.01秒ごとに呼ばれるのでここでelapsedTimeに0.01を足す
    self.elapsedTime += 0.01
}

これでタイマーの起動ができました。

経過時間を表示

タイマー部分ができましたので、次は経過時間を表示していきましょう。

しかしまだ elapsedTime は秒であるので、まずはミリ秒・秒・分に分けていきます。

// ミリ秒は小数点第一位、第二位なので100をかけて100で割った余り
let milliSecond = Int(self.elapsedTime * 100) % 100

// 秒は1・2桁なので60で割った余り
let second = Int(self.elapsedTime) % 60

// 分は経過秒を60で割った余り
let minutes = Int(self.elapsedTime / 60)

これで分けることができました。

ミリ秒は1000ミリ秒で1秒となるため本来は3桁表示が正しいですが、インターバルを0.01秒(10ミリ秒)としているので2桁表示にします。

次は表示です。

self.timerLabel.text = String(format: "%02d:%02d:%02d", minutes, second, milliSecond)

ここでビルドをしてみましょう。

ここまでのソースを載せておきます。

@IBAction func tappedStart(_ sender: Any) {
    Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
        self.elapsedTime += 0.01
        // ミリ秒は小数点第一位、第二位なので100をかけて100で割った余り
        let milliSecond = Int(self.elapsedTime * 100) % 100
        // 秒は1・2桁なので60で割った余り
        let second = Int(self.elapsedTime) % 60
        // 分は経過秒を60で割った余り
        let minutes = Int(self.elapsedTime / 60)
        self.timerLabel.text = String(format: "%02d:%02d:%02d", minutes, second, milliSecond)
    }
}

ストップ機能

次はストップ機能をつけていきましょう。

実は scheduledTimer() はTimerを返り値としています。そのTimerをViewController内で変数として持っておきましょう。

var timer: Timer?

@IBAction func tappedStart(_ sender: Any) {
    timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
        self.elapsedTime += 0.01
        // ミリ秒は小数点第一位、第二位なので100をかけて100で割った余り
        let milliSecond = Int(self.elapsedTime * 100) % 100
        // 秒は1・2桁なので60で割った余り
        let second = Int(self.elapsedTime) % 60
        // 分は経過秒を60で割った余り
        let minutes = Int(self.elapsedTime / 60)
        self.timerLabel.text = String(format: "%02d:%02d:%02d", minutes, second, milliSecond)
    }
}

次は tappedStop 関数の中でタイマーを止めて、 elapsedTime を初期化します。

@IBAction func tappedStop(_ sender: Any) {
    if let t = timer {
        // タイマーを止める
        t.invalidate()
    }
    // 経過時間を0秒に初期化
    elapsedTime = 0.0
}

ストップウォッチアプリ

ここまで実装できたらこんな感じで動きます。

これでタイマーアプリの完成です!

下に今回のソースコードを全て載せておきます!

import UIKit

class ViewController: UIViewController {

    @IBOutlet weak var timerLabel: UILabel!
    var elapsedTime: Float = 0.0
    var timer: Timer?

    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.
    }

    @IBAction func tappedStart(_ sender: Any) {
        timer = Timer.scheduledTimer(withTimeInterval: 0.01, repeats: true) { (timer) in
            self.elapsedTime += 0.01
            // ミリ秒は小数点第一位、第二位なので100をかけて100で割った余り
            let milliSecond = Int(self.elapsedTime * 100) % 100
            // 秒は1・2桁なので60で割った余り
            let second = Int(self.elapsedTime) % 60
            // 分は経過秒を60で割った余り
            let minutes = Int(self.elapsedTime / 60)
            self.timerLabel.text = String(format: "%02d:%02d:%02d", minutes, second, milliSecond)
        }
    }

    @IBAction func tappedStop(_ sender: Any) {
        if let t = timer {
            t.invalidate()
        }
        elapsedTime = 0.0
    }
}

まとめ

お疲れ様でした!

ストップウォッチアプリのチュートリアルを作成しました。

UIやロジックなど基礎が詰まっていますので、2回くらい作成して使い方や考え方を理解していきましょう。