Help us understand the problem. What is going on with this article?

SwfitUIで通信中にIndicatorを表示できるようにする。

SwiftUIでLoadingの画面を表示したい!

先に現在できているコードを記載します。
また今回のコードのIndicator部分については、tsuzuki817さんの作りながら学ぶ! SwiftUIアニメーション インジケーター編を参考にさせていただきました。なのでIndicatorの実装に関する詳細は省略します。

struct LoadingIndicatorView: View {
    @Binding var isLoading: Bool
    @State private var isAnimating = false
    private let animation = Animation.linear(duration: 1).repeatForever(autoreverses: false)

    var body: some View {
        GeometryReader { geometry in
            ZStack {
                // ①Loading中に画面をタップできないようにするためのほぼ透明なLayer
                Color(.black)
                    .opacity(0.01)
                    .frame(width: geometry.size.width, height: geometry.size.height)
                    .edgesIgnoringSafeArea(.all)
                    .disabled(self.isLoading)
                Circle()
                    .trim(from: 0, to: 0.6)
                    .stroke(AngularGradient(gradient: Gradient(colors: [.gray, .white]), center: .center),
                            style: StrokeStyle(
                                lineWidth: 8,
                                lineCap: .round,
                                dash: [0.1, 16],
                                dashPhase: 8))
                    .frame(width: 48, height: 48)
                    .rotationEffect(.degrees(self.isAnimating ? 360 : 0))
                    // ②アニメーションの実装
                    .onAppear() {
                        withAnimation(Animation.linear(duration: 1).repeatForever(autoreverses: false)) {
                            self.isAnimating = true
                        }
                    }
                    .onDisappear() {
                        self.isAnimating = false
                    }
            }
             // ③Loading中だけLoading画面が表示されるようにする。
            .hidden(self.$isLoading.toggle())
        }
    }
}

使う時は、以下のように書きます。これでisLoadingの表示/非表示が切り替わります。

@State private var isLoading = false

var body: some View {
    ZStack {
        Text("Hello world!")
        LoadingIndicatorView(isLoading: self.$isLoading)
    }
}

以下のGifの画面では、真ん中の青いボタンをタップすると、3秒間だけisLoadingtrueになるような実装をしてあるため、3秒間だけLoading画面が出るようになっています。またLoading画面中が表示されている間は、青いボタンはタップできないようになっています。
20200223_023345.GIF

実装詳細

①Loading中に画面をタップできないようにするためのほぼ透明なLayer

Color(.black)
    .opacity(0.01)
    .frame(width: geometry.size.width, height: geometry.size.height)
    .edgesIgnoringSafeArea(.all)
    .disabled(self.isLoading)

なぜ「ほぼ」透明になっているかというと、Color(.clear)にしてしまうと言うとdisabledを指定しても、画面へのジェスチャーが効いてしますからです。これに気づくまで結構時間がかかりました。綺麗な書き方も思いつかなかったので、とりあえず黒いLayerを作成し、それに.opacity(0.01)を設定することで、限りなく透明で、ユーザーのジェスチャーを無効化するLayerを作成することになんとか成功しました。これはSwiftUIのバグではないのか、、、、ちなみに色を.clear以外に指定すると、ちゃんとdisabledが効きます。

②アニメーションの実装
参考資料では以下のように書かれていましたが、これだとLoadingのアニメーションが実行されるたびに、インジェクターが時計回りと反時計回り、交互に回転するようになってしました。

.onAppear() {
    withAnimation(
        Animation
            .linear(duration: 1)) {
                self.isAnimation.toggle()
    }

そこでViewが表示されるonAppear()isAnimationtrueに、非表示になる onDisappear()falseにする実装に変更しました。
これで何度Loading画面を表示しても、ずーと時計回りにIndicatorが回転しています。

.onAppear() {
    withAnimation(self.animation) {
        self.isAnimating = true
    }
}
.onDisappear() {
    self.isAnimating = false
}

③Loading中だけLoading画面が表示されるようにする。
親ViewのStateにBindしているisLoadingフラグに合わせて、Loading画面の表示/非表示が切り替わるような実装を行います。
該当箇所は以下の一行です。

.hidden(self.$isLoading.toggle())

hiddenは、カスタムのModifierです。詳細の実装については、こちらにまとめてあります。
問題はisLoadingの値をそのままhiddenに流すと、isLoadingtrueの時にLoading画面が非表示になってしまいます。そこで流れてくるbooleanの値を反転させる必要があります。RxSwiftで書くと、.map({ !$0 })のようなイメージです。そこでtoggle()というBindingのextensionを作成しました。作り方はこちらの記事を参考にしました。

import SwiftUI
import Combine

extension Binding where Value == Bool {
    func toggle() -> Binding<Bool> {
        Binding<Bool>(get: { () -> Bool in
            return !self.wrappedValue
        }, set: { value in
            self.wrappedValue = value
        })
    }
}

このように書くことで、カスタムのBindingを作成することができます。RxSwiftのカスタムオペレーターを作るようなイメージですね。
このカスタムのBindingを作成する方法は、他にも色々応用できそうです。

まとめ

SwiftUIは1つのファイルにまとめすぎると、かなり読みづらくなる印象があるので、カスタムのViewやModifier、extensionなどを作成して、コードを分割することで、可読性がかなり上がるなと思いました。
まだまだSwiftUIはUIKitと比較するとできることは限られていますが、これからはきっとSwiftUIが主流になっていくと思うので、SwiftUIのキャッチアップも進めていきたいですね。

参考資料

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした