バックグラウンド処理の待機中にViewをぼかしたい

SwiftUI
バックグラウンド処理の待機中にViewをぼかしたい

はじめに

バックグラウンドで何か重たい処理をしていて、処理が終わるまで待機する必要があるときに、インジケータダイアログを出しておくことがあると思います。併せて背景をぼやかして、ちょっとオサレに見せたい時のお話。

例えば、SVProgressHUDのようなイメージです。

GitHub - SVProgressHUD/SVProgressHUD: A clean and lightweight progress HUD for your iOS and tvOS app.
A clean and lightweight progress HUD for your iOS and tvOS app. - SVProgressHUD/SVProgressHUD

ActivityIndicatorを出すまえに、まずは背景をぼやかすところで少し実験をしてみました。

ZStackでぼやかしたViewをかぶせる

方針

まずは安直パターン。ZStackであらかじめ重ねておいて、表示非表示を切り替えます。
ぼやかしたViewを重ねて、下にあるView群がぼやけるかどうかがポイントになります。

コード

ぼやけたViewを用意します。

struct BlurViewV1: View {
    @Binding var isShowing:Bool

    var body: some View {
        Rectangle()
            .fill(Color.gray)
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .opacity(0.6)
            .blur(radius: 3.0)
            .blendMode(.normal)
            .opacity(self.isShowing ? 1.0:0.0)
    }
}

ぼやけたViewを重ねて表示しておきます。もちろん初期状態は非表示で。

struct ContentView: View {
    @State var isShowing:Bool = false
    var body: some View {
        // V1
        ZStack {
            VStack {
                Text("Hello, World!")
                    .font(.largeTitle)

                Button(action: {
                    self.isShowing = true
                    self.blur()
                }) {
                    Text("Blur!")
                }
            }
            BlurViewV1(isShowing: self.$isShowing)
        }
    }

ボタンをタップするとぼやけたViewが表示されます。self.blur()では2秒待って非表示に戻す処理を行っています。

どのように表示されるか

さて、うまくいきますかどうか。

重ねられたViewがぼやけていません

あれ?確かに重なっていますが、ぼやけていないぞ? Hello,World!がはっきり見えています。

ぼやかすモディファイアを付加したViewに、ぼやかしたいViewをプロパティとして保持する

方針

ややこしい言い回しですが、要するにぼやかして表示したいViewのblurモディファイアを使ってぼやかす方法です。

コード

struct BlurViewV2<Content>: View where Content: View {
    @Binding var isShowing:Bool

    var content: () -> Content

    var body: some View {
        ZStack {
            content()
                .blur(radius: isShowing ? 3.0 : 0.0)

            Rectangle()
                .fill(Color.gray)
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .opacity(0.6)
                .blur(radius: 3.0)
                .blendMode(.normal)
                .opacity(self.isShowing ? 1.0:0.0)
        }
    }

ぼやかしたViewをかぶせる時と対比するために、あえてコードをそのまま流用しています。
Viewプロトコルに準拠したジェネリクス型を定義しておき、任意のViewをプロパティとして保持できるようにしています。

ぼやかしたViewをかぶせる時と同様にコーディングしていきます。

struct ContentView: View {
    @State var isShowing:Bool = false
    var body: some View {

        // V2
        BlurViewV2(isShowing: self.$isShowing) {
                VStack {
                Text("Hello, World!")
                    .font(.largeTitle)

                Button(action: {
                    self.isShowing = true
                    self.blur()
                }) {
                    Text("Blur!")
                }
            }
        }
    }

どのように表示されるか

今度はどうでしょう?

重ねられたViewがぼやけています

良き感じにぼやけています。

まとめ

ぼやけたViewをかぶせる安直な方法はお手軽に実装できます。しかしながら、ぼかしまではかぶせたViewには適用されなかった……

すりガラスを通して見るような感じを想定していたんですけどね。

かぶせる方式は重たい処理をしているときに待機させるときに使うというよりも、純粋にボタンなどを操作させないようにするときに使うのが良いかもしれません。ただし、操作できそうに見えて操作できないようにしてしまうため、使い所に注意が必要かもしれません。