概要
本記事では、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()
}
}
}



コメント