概要
本記事では、Slider のつまみの位置を Timer の進捗状況に合わせて動かす方法について説明します。
環境
OS: macOS Monterey 12.3.1
Xcode: 13.3.1
Swift: 5.6.0.323.62
Slider の基本
Slider は、過去記事「Slider の使用方法」で既に説明していますが、簡単に説明すると「つまみをスライドさせて値を変化させる」UI部品です。
通常、つまみはユーザーが手動で動かします。
Slider の基本的な構文は以下の通りです。
Slider の value には、つまみの現在値、in にはつまみの最小値から最大値までの範囲を指定します。
Slider(value: 値, in: 範囲)
Slider の基本使用例を以下に示します。
この例では、Slider の範囲を 0 〜 100 までにして、つまみを動かすと、現在の値が Text に表示されます。
struct ContentView: View { @State private var sliderVal : Double = 0 var body: some View { VStack() { Text(String(format: "%.1f", sliderVal)) Slider(value: $sliderVal, in: 0...100) .padding(.horizontal) Spacer() } } }
つまみの位置をタイマーと連動する
Slider の基本を理解したところで、つまみの位置とタイマーを連動する方法について見ていきます。
Timer.publish
ここで使用するタイマーは Timer.publish で、構文は以下の通りです。
static func publish( every interval: TimeInterval, tolerance: TimeInterval? = nil, on runLoop: RunLoop, in mode: RunLoop.Mode, options: RunLoop.SchedulerOptions? = nil) -> Timer.TimerPublisher
パラメータは以下の通りです。
パラメータ | 説明 |
---|---|
interval | イベントを発行する時間間隔。たとえば、0.5 を指定すると0.5秒ごとにイベントを発行します。 |
tolerance | イベントを発行するときに許可されるタイミングの差異。デフォルトはnil です。これにより任意の分散が可能になります。 |
runLoop | タイマーが実行される実行ループ。 |
mode | タイマーを実行する実行ループモード。 |
options | タイマーに渡されるスケジューラオプション。デフォルトはnil。 |
以下にコード例を示します。
この例では、アプリが起動するととともに、タイマーがスタートします(3行目)。
タイマーの進捗状況は .onReceive で受け取ることができ、Slider の値を更新しています(10行目)。
struct ContentView: View { @State private var sliderVal = 0.0 let timer = Timer.publish(every: 0.1, on: .main, in: .common).autoconnect() var body: some View { VStack { Text(String(format: "%.1f", sliderVal)) Slider(value: $sliderVal, in: 0...100) // 0から100の範囲を指定 .onReceive(timer) { _ in if sliderVal < 100 { sliderVal += 1 } } Spacer() } } }
[Start][Pause][Stop]ボタンを配置し、タイマーに合わせてつまみを動かす
先ほどの例では、アプリの起動と同時に、タイマーがスタートしていました。
今度は、任意のタイミングでタイマーの開始ができるようにしていきます。
ストップウォッチクラスを作成する
せっかくですので、Timer を利用して、開始、一時停止、停止ができるストップウオッチクラスを作成します。
コードは以下の通りです。
// // StopWatch.swift // SliderAndTimer // // Created by 高橋広樹 on 2022/05/14. // import Foundation class StopWatch:ObservableObject{ // ストップウォッチの状態 enum status { case start case stop case pause } @Published var currentStatus:status = .stop @Published var elapsedTime = 0.0 var timer = Timer() // ストップウォッチの開始 func start(maxTime : Double = 1.0){ currentStatus = .start timer = Timer.scheduledTimer(withTimeInterval: 0.1, repeats: true){ timer in self.elapsedTime += 0.1 if maxTime > 0.0 && self.elapsedTime > maxTime { self.stop() } } } } // ストップウォッチの停止 func stop(){ timer.invalidate() elapsedTime = 0 currentStatus = .stop } // ストップウォッチの一時停止 func pause(){ timer.invalidate() currentStatus = .pause } }
13〜17行目は、ストップウオッチの状態を表す enum です。ストップウオッチが開始しているのか、一時停止中なのか、停止中なのかを表します。
19行目は、現在の状態を管理する変数で、データ型は先ほどの enum の status 型です。
20行目は、現在のストップウオッチの経過時間を表します。
この StopWatch クラスには、start(), stop(), pause() のメソッドがあり、それぞれのメソッドが実行されると、タイマーの開始、停止、一時停止が行われます。
タイマーを開始する start() メソッドは 25〜34行目です。
メソッドの引数 maxTime はタイマーの長さ(時間)を秒で指定することができます。引数で指定した時間に到達すると、タイマーは自動で停止します。引数に時間指定がない場合は、stop() メソッドが実行されるまでストップウオッチは動き続けます。
タイマーを停止する stop() メソッドは37〜41行目です。
タイマーを停止するために timer.invalidate() メソッドを実行した後、経過時間 elapsedTime を 0 にしています。
タイマーを一時停止する pause() メソッドは44〜47行目です。
タイマーを一時停止するために timer.invalidate() メソッドを実行します。 経過時間 elapsedTime の値は保持したままにしたいので、ここでは何の操作もしていません。
タイマーに合わせてつまみを動かす
作成した stopWatch クラスを利用して、タイマーに合わせてつまみを動かすアプリを作成します。
このアプリには、[Start][Pause][Stop]ボタンを配置し、[Start]が押されるとタイマーがスタートして、タイマー値に合わせてつまみが移動します。
また、[Pause]ボタンでタイマーが一時停止、[Stop]ボタンでタイマーが停止します。
コード例を以下に示します。
struct ContentView: View { @State private var sliderVal = 0.0 @ObservedObject var stopWatch = StopWatch() var body: some View { VStack { HStack { Button("Start") { stopWatch.start() } Button("Pause") { stopWatch.pause() } Button("Stop") { stopWatch.stop() } } Text(String(format: "%.1f", stopWatch.elapsedTime)) Slider(value: $stopWatch.elapsedTime, in: 0...10) // 0から100の範囲を指定 .padding() Spacer() } } }
コメント