SwiftUI: `.redacted` を使ってインターネットからデータ取得するときに、簡単にプレースホルダーのオーバーレイを追加できます。

APIから情報をフェッチしている間、空白の画面を表示することは避けたいものです。ローディングインジケータを表示するべきでしょう。あるいは、ビューにSwiftUIのプレースホルダ―オーバーレイを設置することもできます。

![image](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/635330/cc991d2b-1862-ba12-946e-f7f13e1bf59d.gif)

この記事では `.redacted` を使って簡単にプレースホルダ―オーバーレイを追加する方法、ビューのプレースホルダーオーバーレイ表示ステータスを監視する方法、プレースホルダーオーバーレイにアニメーションを追加する方法について述べます。

## `.redacted` と `.unredacted()` ビューモディファイア

`.redacted` モディファイアは、データのダウンロードを行っている最中には非表示にしたいビュー要素に追加することができます。

```swift
.redacted(reason: (weatherInformation == nil) ? .placeholder : [])
```

`reason` が `.placeholder` と等しい場合、オーバーレイがビューに追加されます。また、`reason` 変数が空配列と等しい場合は表示されるプレースホルダーオーバーレイはありません。

また、テキストビュー要素用に何らかのデフォルト値を指定する必要もあります。それにより、生成するプレースホルダーのサイズをSwiftUIが把握します。例えば

```swift
Text(weatherInformation?.weatherDescription ?? "気象情報をダウンロードしています...")
```

常にスクリーンに表示しておきたいエレメントに対しては、`.unredacted()` ビューモディファイヤーを追加してください。

### プレイスホルダーを表示するかどうかをチェックするための拡張を追加する

コードに拡張を追加することもできます：

```swift
extension View {
    @ViewBuilder
    func redacted(showPlaceholder: Bool) -> some View {
        if showPlaceholder {
            self
                .redacted(reason: .placeholder)
        } else {
            self
                .unredacted()
        }
    }
}
```

よって `showPlaceholder` 変数によって容易にプレイスホルダーを表示するか隠すかを決定できます

```swift
.redacted(showPlaceholder: (weatherInformation == nil))
```

## SwiftUIビューへのビュー修飾子の適用

まず、基本的な気象情報を表示するSwiftUIビューがあります。 

```swift
struct WeatherView: View {
    
    @Binding var weatherInformation: Weather?
    @Environment(\.redactionReasons) var redactions
    
    var body: some View {
        
        HStack {
             Image("sunnyImage")
                .resizable()
                .scaledToFit()
                .frame(width: 100, height: 100)
                .unredacted()
            VStack(alignment: .leading) {
                Text(weatherInformation?.status ?? "ダウンロードしています...")
                    .font(.largeTitle)
                    .padding(.vertical, 3)
                Text(weatherInformation?.weatherDescription ?? "気象情報をダウンロードしています...")
                    .padding(.vertical, 3)
                Label(weatherInformation?.degrees ?? "...", systemImage: "thermometer.sun")
                    .font(.title2)
                    .padding(.vertical, 3)
            }
        }
        
    }
    
}
```

**1> このビューのコードでは、SwiftUIが自動的にオーバーレイを生成することができるように、テキストとラベルのデフォルト値を指定する必要があります。**
2> 上記の例の `Image` オブジェクトには、修飾子 `.unredacted()` があります。これは、ユーザーに常に表示されることを意味します。

コンテンツ閲覧に作成されたばかりの `WeatherView` でそれを用いることができます。

```swift
Section {
    WeatherView(weatherInformation: $weatherInformation)
        .redacted(reason: (weatherInformation == nil) ? .placeholder : [])
        //.redacted(showPlaceholder: (weatherInformation == nil))
}
```

`WeatherView` 内の画像以外の閲覧対象が表示されるのは `weatherInformation == nil` 条件が当てはまらない場合のみです。

そしてこの例では、`weatherInformation` を設定することができます。

```swift
.onAppear(perform: {
    DispatchQueue.main.asyncAfter(deadline: .now() + 3.5) {
        self.weatherInformation = Weather(status: "晴れ", weatherDescription: "今日の東京の天気は晴れです。", degrees: "23度")
    }
})
```

## プレースホルダーオーバーレイのモニター

`@Environment` 変数 `\.redactionReasons` を使って `WeatherView` に加えられたプレースホルダーオーバーレイの有無をモニターすることができます。

```swift
@Environment(\.redactionReasons) var redactions
if self.redactions.isEmpty {
    Button(action: {}, label: {
        Text("天気予報のビュー表示")
    })
}
```

## ​データのダウンロード中にアニメーションを追加する

データのダウンロード中にアニメーションを追加することもできます。​例えば、プレースホルダーのオーバーレイをぼかすアニメーションを追加したり、不透明度を変更したりすることができます。

​ここでは、ビューにアニメーションを追加するビューのモディファイア `struct` を作成する必要があります。​これはビューの不透明度を1.0から0.3に変更して繰り返す例の1つです。

```swift
struct EaseInOutAnimation: ViewModifier {
    
    @State private var contentOpacity = 1.0
    
    func body(content: Content) -> some View {
        content
            .opacity(contentOpacity)
            .animation(Animation
                        .easeInOut(duration: 2)
                        .repeatForever(autoreverses: true))
            .onAppear { contentOpacity = 0.3 }
    }
    
}
```

この `EaseInOutAnimation` ビュー修飾子をビュー拡張機能の編集済み修飾子に適用することができます：

```swift
extension View {
    @ViewBuilder
    func redacted(showPlaceholder: Bool) -> some View {
        if showPlaceholder {
            self
                .redacted(reason: .placeholder)
                .modifier(EaseInOutAnimation())
        } else {
            self
                .unredacted()
        }
    }
}
```

これで、もし `showPlaceholder` が真であれば、プレースホルダーが表示され、 `EaseInOutAnimation` アニメーションもビューに適用されます。

---

[WeatherView と ContentView コード例を見る](https://mszmagic.com/blog/swiftui-redacted-modifier/)

---

:relaxed: [Twitter @MszPro](https://twitter.com/MszPro)

:sunny: [私の公開されているQiita記事のリストをカテゴリー別にご覧いただけます。](http://mszpro.com/ioskiji/)
<img width="150" src="https://cdn.mszmagic.com/static-web-content/AppClipImage_small.png">