筋トレが趣味なんですけど、 種目間の休憩時間をタイマー管理して一定にしたい。
たくさんの種目を何セットもやるとなると、その度に「アレクサ!●分のタイマーセット!」って何回もいうのめんどくさい。
すでにあるアプリでばっちり自分のニーズにハマるタイマーを探すのもめんどくさい。
複雑な機能はいらない。
とにかく最低限の機能かつ最低限の操作で筋トレ中でもカンタンに触れるアプリが欲しい。
というわけで、Swiftのお勉強を兼ねて自分で作ってみることにしました。
開発環境
MacOS Monterey 12.2.1
Xcode Version 13.4.1
Swift Version 5
画面イメージ
- カウントダウンの時間を分単位で指定
- タイマーはスタート→ストップ(リセット)→再度スタート
※一時停止→再開機能はあえて実装せず
コード
コード全文(Github)
コード全文はこちらを参照下さい。
実装要素(個人的な実装ポイント)
- ボタン押下によるイベント発火
- Combineを利用した繰り返しタイマー処理
- 音声ファイルの再生
- タイマーと連動したプログレスバーの表示
コード内容
ボタン押下時のイベント発火
Button(action: {
if(timerController.timer == nil){
intervalSecond = intervalMinute * 60
timerController.start(Double(intervalSecond))
}else{
timerController.stop()
}
}){
Text("\((timerController.timer != nil) ? "Stop" : "(Re)Start")")
画面中央のストップ/スタートボタン押下時に、指定された分単位のタイマーを開始・中断を交互に行うようタイマーが動いているかどうかで判定し切り替えを行なっています。
(ボタン上の文言も同様)
@Published var timer: AnyCancellable!
...
// タイマー定義
timer = Timer
.publish(
every: interval, // タイマー処理実行間隔
on: .main, // 実行場所(.main:メインスレッドでの実行)
in: .common
)
.autoconnect() // 繰り返し処理の実行
.receive(
on: DispatchQueue.main
)
.sink(
receiveValue: (
{_ in
// 設定した時間ごとに呼ばれる
...
}
)
)
タイマー定義としては単純に繰り返し処理が行えるように定義しているのみです。
音声ファイルの再生
// アラーム音声ファイル読み込み
private let shining_star = try! AVAudioPlayer(data: NSDataAsset(name: "shining_star")!.data)
// 音声ファイル再生
private func shiningStar() {
shining_star.stop() // 停止
shining_star.currentTime = 0.0 // 頭出し※最初から再生するため
shining_star.play() // 再生
}
// 音声ファイル再生停止
private func stopShiningStar() {
shining_star.stop() // 停止
shining_star.currentTime = 0.0 // 頭出し※最初から再生するため
}
事前準備としてAssetにファイルを取り込んでおく必要はありますが、
再生、停止自体は簡単に記述できました。
コードにコメントでも記載していますが、確実に最初から再生されるようにするため、
停止→頭出し→再生 という順番で処理を行なっています。
※function名がshiningStarなのは、フリー音源でお馴染みのシャイニングスターを使わせて貰ったからです。
タイマーと連動したプログレスバーの表示
ProgressView(
value: timerController.remaining,
total: Double(intervalSecond)
)
.accentColor(Color.green)
.scaleEffect(x: 1, y: 15, anchor: .center)
こちらはSwiftUIの部品を使ってみただけですが。
デフォルトだとバーの高さが物足りないので.scaleEffect()
でボリュームを出しています。
以上