@Environmentについて、そろそろ"きちんと"理解したいあなたへ

こんにちは。takasan1234と申します。本記事では、SwiftUI開発で頻繁に目にする`@Environment`について、より深く理解することを目的に解説します。これまで他の記事で見かけるたびになんとなく使っていた方も多いと思います。今日こそしっかりと理解して、エンジニアとしてスキルアップしていきましょう。

## 本記事で得られる情報
・`@Environment`とは
・SwiftUIが用意している`@Environment`プロパティ
・カスタム`@Environment`とは



## 各ツールのバージョン
使用したツールのバージョンは以下のとおりです

Xcode Version 16.1 

## @&#xFFF0;Environmentとは

`@Environment`は、**データを格納できる棚のような仕組み**です。`@Environment`は**さまざまな画面からデータの一時保持、アクセス、管理が可能です**（ただし、永続的な保存はできません）。これは、アプリ全体で共有される設定や状態の管理に特に役立ちます。自分で用意したデータを管理できるだけでなく、SwiftUIが予め用意したデータも利用できます（詳細は後述）。後者を使えば、デバイスの情報や現在の日時などの便利な情報を簡単に取得できます。

![スクリーンショット 2024-12-06 17.47.56.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3879369/e7aeb07a-8bb3-a5e3-e06b-8f1850ab722b.png)

`React`の経験がある方は、「Contextに似ている」と理解しやすいでしょう。

## @&#xFFF0;Environmentの種類
`@Environment`には大きく分けて2種類あります。

・SwiftUIが用意している`@Environment`
・自作の`@Environment`

以下では、これら2種類の@Environmentについて詳しく解説していきます。


## SwiftUIが用意している@&#xFFF0;Environment

SwiftUIで`@Environment`を実際に使う例見てみましょう。
以下は、アプリのカラー・スキーム（ダークモードまたはライトモード）の状態を取得する基本的な例です。
```Swift
struct ContentView: View {
    // 現在のカラー・スキームを取得
    @Environment(\.colorScheme) var colorScheme: ColorScheme

    var body: some View {
        // カラー・スキームによって異なるテキストを表示
        Text(colorScheme == .dark ? "ダークモード" : "ライトモード")
            .padding()
    }
}
```

この行での中で特に重要なのが3行目です。

```Swift
// 3行目のコード
@Environment(\.colorScheme) var colorScheme: ColorScheme
```


この行で、`@Environment`の棚の一つである`colorScheme`の中から値を取り出しています。
<br>

![スクリーンショット 2024-12-06 17.59.05.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3879369/b8ae6b1e-73a3-2c48-af36-e620cbe9af2e.png)



::: note info
**＠Environmentの値にアクセスする方法**（一般形）
```Swift
@Environment(\.<環境キー>) var <環境変数名>: <型>
```
:::

### 環境キー名 一覧
`colorScheme`の他にも、SwiftUIには多くの便利な環境地が用意されています。

| 環境キー | 説明 | 型 | 取り得る値 |
| --- | --- | --- | --- |
| colorScheme | アプリのテーマカラー設定 | ColorScheme | .light（明るい）, .dark（暗い） |
| locale | ユーザーの地域・言語設定 | Locale | en_US, ja_JP など |
| presentationMode | ビューの表示状態 | Binding<PresentationMode> | .presented, .dismissed |
| horizontalSizeClass | 画面の横幅に基づくレイアウト | UserInterfaceSizeClass | .compact（狭い）, .regular（広い） |
| verticalSizeClass | 画面の高さに基づくレイアウト | UserInterfaceSizeClass | .compact（短い）, .regular（長い） |
| accessibilityEnabled | アクセシビリティの状態 | Bool | true（有効）, false（無効） |
| font | フォントの設定 | Font | .body, .title など |
| dynamicTypeSize | 文字サイズの設定 | DynamicTypeSize | .small から .extraExtraExtraLarge |
| scenePhase | アプリの実行状態 | ScenePhase | .active（実行中）, .inactive（一時停止）, .background（背景） |
| openURL | URLを開く機能 | OpenURLAction | URLを開くアクション |

## 自作の@&#xFFF0;Environment
次に独自のデータを`@Environment`として利用する方法を見ていきましょう。以下のサンプルプログラムは、アプリ内で整数カウンターを共有する仕組みを実装したものです。まずは全体像を把握するため、軽く目を通してください。詳細は後ほど解説します。

```Swift
// コード１
extension EnvironmentValues {
    @Entry var count: Int = 0
}

// コード２
struct ContentView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            ChildView()
            Button("Increment") {
                count += 1
            }
        }
        .environment(\.count, count)
    }
}

struct ChildView: View {
    var body: some View {
        CountView()
    }
}

//コード３
struct CountView: View {
    @Environment(\.count) var count: Int

    var body: some View {
        Text("\(count)")
    }
}
```

上記のコードを参考に、カスタム@Environmentの使用方法を3つのステップで解説します。
・領域の確保
・値の更新
・値の取得

![スクリーンショット 2024-12-07 12.44.45.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3879369/146e404f-34ed-e577-8df4-9db4cb772acb.png)


### 領域の確保
`@Environment`に**自分のデータを格納するスペースを確保する方法**について解説します。データを保存するには、まずそのためのスペースを確保する必要がありますよね。



``` Swift:コード１
extension EnvironmentValues {
    @Entry var count: Int = 0
}
```


このコード１では、`@Environment`を使用して`count`の保存領域を確保します。図の青い矢印が示すのが、この処理です。

EnvironmentValuesは@Environmentで使用する値を定義する構造体です。SwiftUIはこの中にcolorSchemeやlocaleなどの値をあらかじめ登録しています。コード１ではEnvironmentValuesを拡張して、独自のcountプロパティを追加しています。

![スクリーンショット 2024-12-06 18.25.48.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3879369/661e697c-ab39-6d53-e0d4-d63d6f1a4f3a.png)

:::note info
**@&#xFFF0;Environmentへの登録方法**（一般形）
```Swift
extension EnvironmentValues {
    @Entry var <プロパティ名>: <データ型> = <初期値>
}
```
:::

### 値の更新

```Swift:コード２
struct ContentView: View {
    @State private var count = 0

    var body: some View {
        VStack {
            ChildView()
            Button("Increment") {
                count += 1
            }
        }
        .environment(\.count, count)
    }
}
```

`.environment(\.count, count)`は親ビューの`count`変数を環境変数として子ビューに渡します。これにより、子ビューから`count`の最新の値にアクセスできます。

![スクリーンショット 2024-12-07 12.34.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3879369/348009d8-30f6-3df8-7728-1733d77eea3c.png)

:::note info
**値の更新方法**(一般形)
```Swift
.environment(\.<環境キー>, <値>)
```
:::

### 値の取得
先ほどのSwiftUI標準の@￰Environmentと同様に、以下の方法で値を取得できます。



```Swift:コード３
struct CountView: View {
    @Environment(\.count) var count: Int

    var body: some View {
        Text("\(count)")
    }
}
```
![スクリーンショット 2024-12-07 12.45.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3879369/4fcedb18-5e39-2231-3bdd-14d716ce3926.png)


::: note info
**＠Environmentの値にアクセスする方法**（一般形）
```Swift
@Environment(\.<環境キー>) var <環境変数名>: <型>
```
:::


## まとめ
### SwiftUIが用意している@￰Environmentの利用
::: note info
**＠Environmentの値にアクセスする方法**（一般形）
```Swift
@Environment(\.<環境キー>) var <環境変数名>: <型>
```
:::

### カスタム@&#xFFF0;Environmentの利用
:::note info
**@&#xFFF0;Environmentへの登録方法**（一般形）
```Swift
extension EnvironmentValues {
    @Entry var <プロパティ名>: <データ型> = <初期値>
}
```

**値の更新方法**(一般形)
```Swift
.environment(\.<環境キー>, <値>)
```

**＠Environmentの値にアクセスする方法**（一般形）
```Swift
@Environment(\.<環境キー>) var <環境変数名>: <型>
```
:::

## 最後に
少しでも参考になれば幸いです。
最後までお読み頂きありがとうございました！！

## 参考記事
https://qiita.com/REON/items/b0641204bb921691455b

https://qiita.com/johnjohnjohnjohn/items/2f4c8431d5371cd6794b

https://qiita.com/lovee/items/0893d3ed7813e66d8188