SwiftUI ローディングスピナー

フラグのディレイ

SwiftUIで「重い処理が始まったらすぐスピナーを出す」のではなく、「少し待って、まだ処理中ならスピナーを出す」というパターンはよくあります。短時間で終わる処理でもスピナーが一瞬表示されるのを防げるためです。

最近のSwift Concurrencyを使うなら、ローディング表示用のTaskを別で起動し、処理完了時にキャンセルする方法がシンプルです。

処理の流れ:

  1. 重い処理開始
  2. 300ms待つTaskを起動
  3. 300ms以内に処理が終わればspinnerTaskをキャンセル
  4. 300msを超えたらスピナー表示
  5. 処理完了時にスピナー非表示

View側は普通に:

ZStack {
    ContentView()

    if isLoading {
        ProgressView()
    }
}

ViewModelに寄せる場合

実際は @Observable や ObservableObject のViewModelにまとめることが多いです。

さらに良くするなら「最小表示時間」も入れる

UX的には

  • 300ms以上かかったら表示
  • 表示したら最低500msは表示する

という実装もよくあります。そうしないと表示直後に消えてチラつくためです。

実務では

  • 0〜200ms: 何も表示しない
  • 200〜500ms: スピナー表示
  • 数秒以上: スピナー+メッセージ

くらいが一般的です。SwiftUI + async/await なら「遅延表示用Taskを作ってキャンセルする」が最も自然で保守しやすい実装だと思います。

ローディングスピナー本体

ProgressView() のデフォルトのスピナーは、iOSの標準スタイルに従うので、多くの場合はライト/ダークモードに自動対応します。

ProgressView()

まずはこれが推奨です。

ただし、背景の上にオーバーレイ表示する場合は、背景とのコントラスト次第で見づらくなることがあります。

例えばこんなケースです。

ZStack {
    content

    if isLoading {
        ProgressView()
    }
}

背景が白っぽいとライトモードで見えにくかったり、背景が黒っぽいとダークモードで埋もれたりします。

その場合は、スピナーの背後に半透明のマテリアルや背景を置くのが一般的です。

または

tintを固定しない方がよい

よくあるのが

ProgressView()
    .tint(.white)

ですが、これだとライトモードで見えなくなる可能性があります。

逆に

ProgressView()
    .tint(.black)

だとダークモードで見えなくなります。

どうしても色を指定するならシステムカラーを使います。

ProgressView()
    .tint(.primary)

ただし、標準のままが最もテーマ適応性が高いです。

全画面ローディングなら

実務ではこんな感じが多いです。

これならライトモード・ダークモードの両方で十分なコントラストが確保でき、アクセシビリティ面でも比較的安定します。

タイトルとURLをコピーしました