
一定時間ごとに処理を繰り返したいときに便利なのが Timer.scheduledTimer
です。
アニメーションの更新、定期的なデータ取得、UIのカウントダウン表示など、様々なタイミング制御に活用されます。
SwiftUIでは、UIKitと異なりView
のライフサイクルに注意する必要があります。
この記事では、SwiftUIでのTimer.scheduledTimer
の基本的な使い方から、引数の意味、活用シーン、注意点までをわかりやすく解説します。
Timer.scheduledTimerとは?
Timer.scheduledTimer
は、一定の間隔で処理を繰り返すタイマーを作成し、自動的に現在のRunLoopに追加してくれます。
従来の Timer(timeInterval:target:selector:repeats:)
に比べて、ブロックで直接処理を書けるため、より簡潔で安全に記述できます。
ただし、SwiftUIのView
の中で直接呼ぶのではなく、状態管理(@StateObject / @ObservableObject)や.onAppear
などに組み込むのが基本になります。
基本の使い方
以下のようにonAppear
内でタイマーを起動し、状態変数を更新します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import SwiftUI struct ContentView: View { @State private var counter = 0 @State private var timer: Timer? var body: some View { Text("カウント: \(counter)") .font(.title) .onAppear { timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in counter += 1 } } .onDisappear { timer?.invalidate() // 画面が閉じられたら停止 } } } |
このコードでは、onAppear
でタイマーを起動し、onDisappear
で必ず停止するようにして、メモリリークや余計な処理を防いでいます。
具体例:スタートボタンでタイマーを開始する
ユーザー操作で明示的にタイマーを開始したい場合は、ボタンと組み合わせることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
import SwiftUI struct StartButtonTimerView: View { @State private var counter = 0 @State private var timer: Timer? var body: some View { VStack(spacing: 20) { Text("カウント: \(counter)") .font(.title) Button("スタート") { timer?.invalidate() // 既存のタイマーを止める(多重起動防止) timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in counter += 1 } } Button("ストップ") { timer?.invalidate() } } .padding() } } |
これにより、ユーザーが「スタート」を押したときにカウントアップが始まり、「ストップ」を押すと終了します。
繰り返しをしない例(1回だけ)
1 2 3 4 5 |
.onAppear { Timer.scheduledTimer(withTimeInterval: 3.0, repeats: false) { _ in print("3秒後に1回だけ実行") } }</code><code class="language-swift"> |
主要な引数とその意味
Timer.scheduledTimer
を正しく活用するためには、それぞれの引数の役割を理解することが重要です。
まず、このメソッドのシグネチャ(イニシャライザーに相当する呼び出し形式)を確認しておきましょう。
1 2 3 4 5 6 |
Timer.scheduledTimer( withTimeInterval interval: TimeInterval, repeats: Bool, block: @escaping (Timer) -> Void ) |
このように、Timer.scheduledTimer
は 時間間隔(interval)・繰り返し有無(repeats)・実行内容(block) の3つを指定するだけでシンプルに使える設計になっています。
引数名 | 型 | 説明 |
---|---|---|
withTimeInterval | TimeInterval(Double) | タイマーが発火する間隔(秒数)を指定します。 例えば 1.0 とすれば1秒ごとに処理が呼ばれます。ミリ秒単位で細かい制御も可能です。 |
repeats | Bool | 繰り返すかどうかを指定します。true なら指定した間隔ごとに繰り返し処理が走り、false なら一度だけ実行されます。 |
block | (Timer) -> Void | タイマー発火時に実行されるクロージャ(処理の中身)を定義します。 クロージャには呼び出し元の Timer インスタンスが渡されるので、処理の中で invalidate() を呼んで停止させることも可能です。 |
つまり、「いつ実行するか(withTimeInterval)」「何回実行するか(repeats)」「何を実行するか(block)」 の3点を指定するだけで、シンプルに時間制御を組み込めます。
また、block
内で timer.invalidate()
を呼ぶことで、必要に応じて実行中のタイマーを止められる点も実務上よく使われます。
活用シーン
Timer.scheduledTimer
はユーザー体験の改善やバックグラウンド処理に役立ちます。
代表的な活用シーンは以下の通りです。
- カウントダウンタイマーの実装
例:試験やクイズアプリで残り時間を表示する。 - 一定間隔でのUI更新(進捗バーなど)
例:処理の進捗を視覚的に伝えるアニメーション更新。 - バックエンドへの定期的なポーリング
例:一定時間ごとにサーバーへリクエストを投げて新着データを確認。 - アニメーションのトリガー
例:数秒ごとに画像を切り替えるスライドショー。 - ユーザーの無操作検出(インアクティブ監視)
例:数分間操作がない場合に警告やスクリーンセーバーを起動。
このように、UIまわりからバックエンド処理まで幅広く利用可能で、アプリに「時間的な動き」を加えるうえで欠かせない仕組みです。
注意点
非常に便利な一方で、設計を誤るとメモリリークや意図しない処理が残り続けるリスクがあります。
利用時には次のポイントに注意してください。
- RunLoopへの登録に依存する
タイマーはRunLoopに登録されるため、スレッドが止まっていると動作しません。通常はメインスレッドで十分ですが、バックグラウンドタスクでは注意が必要です。 - SwiftUIのライフサイクルとの整合性
View
が破棄されてもタイマーが残り続けることがあります。onDisappear
で必ずinvalidate()
を呼び、不要なタイマーを終了させましょう。 - 強参照サイクル(retain cycle)の回避
クロージャ内でself
をそのまま使うと強参照が発生し、インスタンスが解放されなくなります。必ず[weak self]
を使って循環参照を避けましょう。 - 用途に応じた代替手段の検討
高精度なタイミング制御やSwift Concurrencyとの相性を考えると、DispatchSourceTimer
やTask.sleep
を選んだ方がよい場合もあります。
「どこで」「どのように」タイマーを管理するかを意識しないと、予期せぬ挙動やパフォーマンス低下の原因になりかねないので、その点は注意が必要です。
まとめ
Timer.scheduledTimer
はSwiftUIでも利用可能ですが、Viewライフサイクルに応じた管理が必須です。
- 一定時間ごとに処理を実行できる
- クロージャで直接記述できるためコードが簡潔
onAppear
/onDisappear
またはボタン操作と組み合わせて安全に管理できる- Swift ConcurrencyやCombineベースのアプローチと比較してもシンプルに導入できる
時間制御が必要なアプリ開発では欠かせない仕組みなので、SwiftUIでも正しく扱えるようにしておきましょう!