swift アプリ開発

非同期処理で読み込み中(ProgressView)の表示/非表示を切り替える

こんにちは、こちょすです!

今回はSwiftUIについての記事デス!

 

スマホアプリを開発しようと思っている方の中には、複雑な処理でレスポンスを返すのに時間かかるようなものを作る方もいると思います。

 

処理に時間がかかって画面がずっと固まっていると、「あれ、エラー?」「あれ、再起動したほうがいい?」となる可能性があり、ユーザビリティが下がってしまいます。

そんなときに使うのがローディング表示(通称くるくる)です。「今読み込み中ですよー」をユーザに伝えることができます。

 

スマホアプリにおいて、読み込み中のローディング表示(ぐるぐる)を、ステータスに合わせて表示したり非表示にする制御には、ひと工夫が必要です。

 

 

仕組みから丁寧に解説していきます!

ここで宣伝です!インスタ始めました!技術系のアカウントと筋トレ系のアカウントです。

ぜひフォロー・応援お願いします!

instagram:

・筋トレ系:@kochos05
・技術系:@kochos_engineer

Twitter:@kochos5

 

 

こんな方におすすめ

  • SwiftUIでローディングの表示/非表示を実装したい
  • SwiftUIのProgressViewの使い方がいまいち分からない
  • 非同期処理について知りたい

 

ではいきましょう!🔥

まずはSwiftUIでProgressViewを表示してみる

ProgressViewというのは、iOS14で実装されたSwiftUIの機能です。

Progressというだけあって、進捗を表現するためのViewですね。

ProgressViewにはStyleが2つあります。

.progressViewStyle(CircularProgressViewStyle())

いわゆるクルクルするやつです。丸くて処理中の間は回り続けます。

ページの読み込み時などに見たことが方が多いのではないでしょうか

.progressViewStyle(LinearProgressViewStyle())

こちらはゲージタイプのビューです。ダウンロードだったり処理などの進捗具合によって色塗りの部分が変わっていきます。

 

今回はProgressViewの表示・非表示の切り替えの制御について書いていくので、実装がシンプルなCircularProgressViewStyle()(くるくるのほう)を使っていきます。

 

やり方さえわかってしまえば、スタイルと進捗の更新部分をゲージタイプに合わせて実装すればよいだけなので、ゲージタイプのほうを最終的に実装したい方もぜひ最後までお付き合いください!

 

以下のようなコードを書いて、ProgressViewを表示してみましょう!

ProgressViewの実装コード

 
import SwiftUI

struct ContentView: View {
    var body: some View {
        
        ZStack {
            Text("Hello, world!")
                .padding()
            Color.gray.opacity(0.5)
            ProgressView()
        }
    }
}

struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

軽く解説しておくと、ZStackでTextの上から透過させたグレーの背景色とProgressView()を重ねているだけです!

 

ProgressViewは今はくるくると回り続けますが、この表示/非表示を制御してみます。

 

ProgressViewの表示/非表示を制御してみる

表示/非表示を管理する、Bool型の変数を用意し、これがtrueであれば表示、falseであれば非表示とします。

import SwiftUI
struct ContentView: View {
    @State var isPresentedProgressView = false
    var body: some View {
        ZStack {
            Text("Hello, world!") .padding()
            if isPresentedProgressView {
                Color.gray.opacity(0.5)
                ProgressView()
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

「@State var isPresentedProgressView = false」を追加して、背景色とProgressView()をifで囲みました。

この状態で起動すると、くるくるは表示されなくなります。

 

変数の値をtrueにすれば、これまでどおり表示されます。

 

 

次はこの変数の値を、ボタンで変えて、表示、非表示を切り替えられるようにしてみます。

ボタンでProgressViewの表示/非表示を制御してみる

 

import SwiftUI
struct ContentView: View {
    @State var isPresentedProgressView = true
    var body: some View {
        VStack {
            Button(action: {
                isPresentedProgressView.toggle()
            }){ Text("表示/非表示切り替え")}
            ZStack {
                Text("Hello, world!") .padding()
                if isPresentedProgressView {
                    Color.gray.opacity(0.5)
                    ProgressView()
                }
            }
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

 

大きな変更点は2つです。

ポイント1

1、VStackでButtonとこれまでのViewを縦に並べた

👉単純にボタンを表示してしまうと、ProgressViewを表示した際に、ボタンが裏側に行ってしまい押せなくなってしまうため、このようにしています

 

ポイント2

2、ButtonのアクションにisPresentedProgressView.toggle()を追加した

👉toggle()というのは、Bool型の変数に対して、反対の値を設定します。(現在trueであればfalseに、現在falseであればtrueにします)

 

この状態でアプリを起動してみると、以下のようにボタンを押すたびにProgressViewが表示されたり非表示になったりします。

 

toggleの後ろにsleepを入れたらどうなるか

では次に、buttonのアクションのtoggleの後ろに、sleepを3秒入れ、その後もう一度toggleをするとどうなるでしょうか。

toggleでtrueにした瞬間にProgressViewが表示されて、3秒間表示されたあと、また非表示になるのでしょうか??

 

import SwiftUI
struct ContentView: View {
    @State var isPresentedProgressView = false
    var body: some View {
        VStack {
            Button(action: {
                isPresentedProgressView.toggle()
                sleep(3)
                isPresentedProgressView.toggle()
            }){ Text("表示/非表示切り替え")}
            ZStack {
                Text("Hello, world!") .padding()
                if isPresentedProgressView {
                    Color.gray.opacity(0.5)
                    ProgressView()
                }
            }
        }
    }
}

 

上記のようなコードを書いて試してみましょう!

 

あれれ、、、何も起こりませんでした。。

これは何が起きているかというと、ボタンのアクションとして、toggle()⇨sleep⇨toggle()が終わった後に、再描画しているということなのです。

つまりtrue⇨sleep⇨falseとなって、最終的にfalseになっているので何も表示されないのです。

 

でも今回僕がやりたいのは、

ボタンを押した時に一度処理を終わらせて、trueとして再描画して、ProgressViewを表示させ、sleepを抜けてfalseになったらProgressViewを非表示にさせる

 

ということです。(ここでいうsleepは実際には業務処理を意味します)

 

非同期処理を実装する

上記のような処理を実装するには、非同期処理を使います。

非同期処理はざっくりとイメージで伝えると、

先輩「こちょすさん、これやってもらえる??あとで状況教えてね!」
後輩「承知しましたー!」

・・・(数時間後)・・・

後輩「終わりました!」

 

こんな感じのイメージです!笑 ここでいう「承知しましたー!」をボタン押下時に返し、一旦そこで会話は終了させた上で、裏では処理が動いており、処理が終わると結果を返してくれるというイメージです。

今回は、ボタン押下時にProgressViewを表示し、3秒後に非表示にするような非同期処理にします。

コードは以下のとおりです。

 

import SwiftUI
struct ContentView: View {
    @State var isPresentedProgressView = false
    var body: some View {
        VStack {
            Button(action: manageProgress){ Text("表示/非表示切り替え")}
            ZStack {
                Text("Hello, world!") .padding()
                if isPresentedProgressView {
                    Color.gray.opacity(0.5)
                    ProgressView()
                }
            }
        }
    }
    private func manageProgress() {
        // ProgressView 表示
        isPresentedProgressView = true
        // 3秒後に非表示に
        DispatchQueue.main.asyncAfter(deadline: .now() + 3.0) {
            self.isPresentedProgressView = false
        }
    }
    
}

ポイントは、DispatchQueueという非同期処理を実装し、asyncAfterで3秒後に変数をfalseに変更するようなタスクを追加しています。

参考記事:Swift GCD入門

 

では挙動を見てみましょう!

 

 

うまくいきましたね!あとはsleepのところを自分のやりたい処理を組み込んであげればOKです!

 

今回は初めてSwiftUIの記事を書いてみましたがいかがでしたでしょうか。

今後はpythonでAPIを構築しつつ、フロントはSwiftUIで作るようなアプリについても書いていければと思います!

 

ではまた!最後まで見ていただきありがとうございます!

-swift, アプリ開発

© 2022 これブロ