📖

EnvironmentKey.defaultValueを変更する

2024/06/15に公開

表題の通りなのですが「あ、それいけるんだ」と発見があったので書きます。

Environment

SwiftUIを使っている方には馴染み深いEnvironmentという仕組みがあります。これは親のViewが子Viewに値を共有するための仕組みです。
Environment: https://developer.apple.com/documentation/swiftui/environment

通常、defaultValueを設定して、変更が必要であれば environment(::)を使用します。以下のコードは Hello から こんにちは に変更しているコードです。ComponentA,ComponentBで @Environment(\.hello) var hello を使う時値は こんにちは になります

struct HelloEnvironmentKey: EnvironmentKey {
  static var defaultValue: String = "Hello"
}

extension EnvironmentValues {
  var hello: String {
    get { self[HelloEnvironmentKey.self] }
    set { self[HelloEnvironmentKey.self] = newValue }
  }
}

VStack {
  ComponentA()
  ComponentB()
}
.environment(\.hello, "こんにちは")

そんな生活を送っていたのですが、defaultValueを変更もできるのでは。と思い試してみました。

試してみた

下のコードで試しました

import SwiftUI

struct ContentView: View {
  @State var isPresented = false
  var body: some View {
    let _ = Self._printChanges()
    VStack {
      Button {
        HelloEnvironmentKey.defaultValue = "こんにちは"
      } label: {
        Text("Change")
      }

      Button {
        isPresented = true
      } label: {
        Text("Present sheet")
      }
      .sheet(isPresented: $isPresented, content: {
        ModalView()
      })
    }
    .padding()
  }
}

#Preview {
  ContentView()
}

struct ModalView: View {
  @Environment(\.hello) var hello
  var body: some View {
    Text(hello)
  }
}

struct HelloEnvironmentKey: EnvironmentKey {
  static var defaultValue: String = "Hello"
}

extension EnvironmentValues {
  var hello: String {
    get { self[HelloEnvironmentKey.self] }
    set { self[HelloEnvironmentKey.self] = newValue }
  }
}

このコードで Change を押さずにモーダルを表示すると Hello と表示され、Change を押した後にモーダルを出すと こんにちは と表示されました。念のため Self._printChanges を挟みましたが Change を押した時にViewの再評価は実行されないです。

使い所

無いです(`・ω・´)キリ。ただ、iOS14時代のSwiftUIではモーダル表示でEnvironmentが引き継がれない問題に遭遇した & そのことについて言及している記事も見かけました。その不具合と戦う環境の人は defaultValueを設定するのも一つの手段になるかもしれないです。

かくいう私もたまにEnvironment引き継がれないよなー。と思い昔は過ごしていました。最近その問題に遭遇してないのでもしかしたら新しいOSでは発生しないかも知れないですね。公式の発表では無いのですが見つけた記事を貼るのでご参考までに

参考:

とはいえ、アプリ起動後で使用したいEnvironmentのdefaultValueを設定したいシチュはあると思います。その場合は専用のViewを用意するなりして親の処理が解決したら子供のViewを評価する方法をとると良さそうです。下のような書き方ですね。もし、defaultValueを設定し直すときはとても慎重に判断しましょう。SSoTが崩れる可能性があります。

https://x.com/bannzai/status/1801413794763313387

まとめ

ちょっとした小ネタでした。この記事でわかることは EnvironmentValuesのgetの処理のフォールバックの先は常に EnvironmentKey.defaultValue を見ていることがわかった点だと思います。これで少しSwiftUIの内側に詳しくなれたと思えばこの記事は超優良記事ですね。実務で全く役に立たないと思ってたけどこんなに良い記事になるとは書いてて思いませんでした。いやー、よかった。良い子のみんなは真似しないでね

おしまい \(^o^)/

Discussion