LoginSignup
3
2

More than 1 year has passed since last update.

【SwiftUI】よくあるインジケーター表示切り替えを、ProgressViewとカスタム ViewModifierで使いやすくする

Last updated at Posted at 2021-09-08

はじめに

SwiftUIには、ローディング中とかにぐるぐるするインジケーターがProgressViewとして用意されています。

大抵こういうViewは共通化されると思うので、使いやすくするためにProgressViewの カスタム ViewModifierを作ってみました。

普通にViewにしてもいいと思ったのですが、
Viewにすると多分こうなってしまうと思います↓

使う側では、毎回ZStackで囲わないといけないので、面倒な気がします。

ZStack {
  Text("Hello World")

  if isShownProgressView {
    CustomProgressView()
  }
}

そうではなくこうやって使えた方が、ZStackとか不要だし便利だろうと思ったので、ViewModifierで実装してます。↓

Text("Hello World")
  .customProgressView($isShownProgressView)

成果物

よくあるローディング中画面が表示されています。
動画で見たい場合は、以下のGithubリポジトリで公開しているので、そのREADMEを確認ください。

ColorProgressViewの前の階層に入れているので、この画面が表示されているときは後ろは操作不可にしてます。
ProgressViewだけでは、後ろは操作不可にしてくれませんので、自分で実装必要でした。

実装を見ればわかりますが、
「読み込み中」のテキストや色、背景色は自由にカスタマイズできます。

Simulator Screen Shot - iPhone 12 - 2021-09-08 at 19.30.12.png

実装

まずカスタム ViewModifierの実装です。

import SwiftUI

struct CustomProgressView: ViewModifier {

    @Binding var isShownProgressView: Bool

    func body(content: Content) -> some View {
        // contentはこのカスタムViewModifierを使用する対象Viewのプロキシ
        ZStack { content
            if isShownProgressView {
                // ProgressViewの背景をタップ不可にするために、Colorを使用
                Color.gray.opacity(0.2)

                VStack(spacing: 6) {
                    ProgressView()
                        .progressViewStyle(CircularProgressViewStyle(tint: .gray))
                        .scaledToFit()
                        .frame(width: 22, height: 22)

                    Text("読み込み中")
                        .foregroundColor(Color.gray)
                        .font(.caption2)
                }
            }
        }
    }
}

最初contentを書くのを忘れてしまい、ProgressViewは表示されるが背景にあるはずのViewが全く表示されないという現象で悩んでました。
content

修飾する対象のViewのプロキシ

だそうなので、これがないと修飾する対象のViewが表示されないのは当然でした。。

なので、当然if文の外にcontentが存在してる必要があります。
そうでないと、isShownProgressViewfalseの時には修飾する対象のViewも一緒に非表示になってしまいます。

ちなみにTextのViewを使わなくても、
ProgressView("読み込み")というようにテキストを設定できるのですが、
フォントサイズや色など細かくカスタマイズしたい場合は、TextのViewを自分で作らないといけないみたいです。
ProgressView(Text("読み込み").font(.caption2))というようには書けなかったです。

では次に、ViewのExtensionを実装します。

extension View {
    func customProgressView(_ isShownProgressView: Binding<Bool>) -> some View {
        self.modifier(CustomProgressView(isShownProgressView: isShownProgressView))
    }
}

これで、初めに書いたように
.customProgressView($isShownProgressView)
とModifierをつければ例の画面が表示できるようになります。

Extensionでラップしない場合は
.modifier(CustomProgressView(showProgressView: $showProgressView))
と書くことになると思います。(実際にこれで動作確認はしていないのですが動くはず。。)

これはこれでいいかもですが、私はラップした方が他の標準Modifierと同じように使えてシンプルかなと思っています。

今更な説明ですが、親View側でshowProgressViewの真偽を切り替えることになると思うので、@Bindingを使っています。

では早速使ってみる。

struct ContentView: View {
    @ObservedObject private var viewModel = ContentViewModel()

    var body: some View {

        VStack(spacing: 50) {
            Text("テスト1")
            Text("テスト2")
        }
        .background(Color.yellow)
        .customProgressView($viewModel.isShownProgressView)
        .onAppear {
            viewModel.onAppear()
        }
    }
}

viewModelクラスのプロパティとして、isShownProgressView@Publishedで持っています。

class ContentViewModel: ObservableObject {

    @Published var isShownProgressView = false

    func onAppear() {
        showProgressView = true

        // 3秒後待ってインジケータを削除(実際はAPI通信したり、画像取得したり)
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            self.isShownProgressView = false
        }
    }
}

今回は3秒待つだけにしていますが、実際はAPIと通信したりとかとにかく何か時間がかかる処理が実装されることになると思います。

その処理完了の前後でisShownProgressViewの真偽を切り替え、その値を監視することによって、
今回のローディング画面の切り替えを行なっています。

参考にした記事

おわりに

Custom ViewModifierを勉強する良い機会になりました。
まだまだSwiftUI初心者ですが、ちょっとずつ進歩してることを願ってます。
誤り、もっと良いやり方あるなどありましたら、是非コメントで教えてください :blush:

誰かの役に立てば幸いです。

3
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
2