【SwiftUI】@Environmentを理解したい

# はじめに
今回は`@Environment`のアウトプットとして書きます。

# 今回作成するもの
ボタンを押すとピンクの画面が表示され、閉じるボタンを押すとピンクの画面が閉じるような簡単なものを例に作成してみます。

![Simulator-Screen-Recording-iPhone-14-Pro-2023-04-10-at-07.19.27.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/954451/c8a78f4b-6158-31e8-d4c9-99726429158d.gif)

今回は`@Environment`を使用する前に、[以前](https://qiita.com/REON/items/ce07417a8b11cccd8f4f)にやった`@Binding`を用いて画面の切り替えをまずは行なってみます。

# `@Binding`を用いて作成する
まずは初めの白い画面を作成します。

```swift
struct ContentView: View {
    @State var isShow = false
    var body: some View {
        Button(action: {
            isShow = true
        }, label: {
            Text("画面を表示")
                .font(.largeTitle)
        })
        .fullScreenCover(isPresented: $isShow) {
            SecondView(isPresented: $isShow)
        }
    }
}
```

ピンクの画面(`SecondView`)が表示するかどうかを管理する`isShow`プロパティを用意します。
`Button`をタップすると`SecondView`を表示させるので`Button action`で`isShow = true`とします。
[fullScreenCover](https://developer.apple.com/documentation/swiftui/view/fullscreencover(ispresented:ondismiss:content:))の`isPresented`引数の真偽値によって画面が表示されるかどうか決まり、`content`引数はクロージャで`Content`を返すので、ここに表示させたい`View(SecondView)`を書きます。

```swift
struct SecondView: View {
    @Binding var isPresented: Bool
    var body: some View {
        NavigationView {
            VStack {
                Text(isPresented ? "表示されている" : "非表示になった")
                    .font(.largeTitle)
                    .foregroundColor(.green)
                Button(action: {
                    isPresented = false
                }, label: {
                    Text("閉じる")
                        .font(.largeTitle)
                })
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.pink)
        }
    }
}
```

今回の例では`SecondView`から`ContentView`に閉じるボタンがタップされたら`ContentView`の`isShow`を`false`にして`SecondView`を閉じなければならないので、`SecondView`の`isPresented`と`ContentView`の`isShow`を`@Binding`によって結び、`isPresented`が変更されたらその変更を参照させている`isShow`に知らせます。
`Button action`で`isPresented = false`とします。

```swift
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
        SecondView(isPresented: Binding.constant(false))
    }
}
```
また、プレビューでも`SecondView`を表示させたいので`ContentView_Previews`に`SecondView(isPresented: Binding.constant(false))`を追加します。
`Binding.constant(false)`とすることで`SecondView isPresented`に`false`を初期値として入れることができます。

# `@Environment`を使って書き換える
ここから本題ですが、先ほどの`@Binding`を使って作成したものを`@Environment`を使って書き換えます。
iOS14とiOS15以降で変更された箇所があるので両方記載してみます。

## iOS14の場合
```swift
struct SecondView: View {
    @Environment(\.presentationMode) var presentation
    var body: some View {
        NavigationView {
            VStack {
                Text(presentation.wrappedValue.isPresented ? "表示されている" : "非表示になった")
                    .font(.largeTitle)
                    .foregroundColor(.green)
                Button(action: {
                    presentation.wrappedValue.dismiss()
                }, label: {
                    Text("閉じる")
                        .font(.largeTitle)
                })
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.pink)
        }
    }
}
```

`@Binding`の部分を`@Environment(\.presentationMode) var presentation`に変更します。これは、`@Environment`を用いて[EnvironmentValues](https://developer.apple.com/documentation/swiftui/environmentvalues)の一つである`presentationMode`にアクセスする変数`presentaion`を定義しています。
この変数を使うことで画面の状態(`presentation.wrappedValue.isPresented`)や画面を閉じる処理(`presentation.wrappedValue.dismiss()`)を扱うことができます。
`wrappedValue`でラップされている内部変数を使って`isPresented`や`dismiss()`で処理することができます。

```swift
struct ContentView: View {
    @State var isShow = false
    var body: some View {
        Button(action: {
            isShow = true
        }, label: {
            Text("画面を表示")
                .font(.largeTitle)
        })
        .fullScreenCover(isPresented: $isShow) {
            SecondView()
        }
    }
}

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

`ContentView`と`ContentView_Previews`でそれぞれ`SecondView`をインスタンス化している部分はそれぞれ`@Binding`から`@Environment`に変更されたので初期値を入れる必要はなくなりました。

## iOS15以降の場合
先ほどの`presentaionMode`を使用する方法は残念ながらiOS14から非推奨になってしまいました。こちらではiOS15からの方法を書いてみます。

```swift
struct SecondView: View {
//    @Environment(\.presentationMode) var presentation
    @Environment(\.isPresented) var isPresented
    @Environment(\.dismiss) var dismiss
    var body: some View {
        NavigationView {
            VStack {
                Text(isPresented ? "表示されている" : "非表示になった")
                    .font(.largeTitle)
                    .foregroundColor(.green)
                Button(action: {
                    dismiss()
                }, label: {
                    Text("閉じる")
                        .font(.largeTitle)
                })
            }
            .frame(maxWidth: .infinity, maxHeight: .infinity)
            .background(.pink)
        }
    }
}
```

`ContentView`と`ContentView_Previews`に変更はありません。
`SecondView`の`@Environment`の`EnvironmentValues`が`isPresented`と`dismiss`になっています。

```swift
@Environment(\.isPresented) var isPresented
@Environment(\.dismiss) var dismiss
```

さらに、`presentation.wrappedValue.isPresented`は`isPresented`になり、`presentation.wrappedValue.dismiss()`は`dismiss()`とシンプルになっています。

ちなみに、`dismiss()`の部分は`dismiss.callAsFunction()`としても同じようです。

iOS14までの`presentationMode`からもともと提供されていた`isPresented`と`dismiss`ですが、iOS15からは`EnvironmentValues`として扱いやすくなりました。

[こちら](https://developer.apple.com/documentation/swiftui/environmentvalues#accessibility)のドキュメントに`isPresented`や`dismiss`以外の`EnvironmentValues`が載っているので一度見てみても面白いかもしれません。

# おわりに
SwiftUI学習始めたばかりなので、間違ってる点がありましたらTwitterまたはコメントで"やさしく"教えてくださいませ。