【SwiftUI】Environment の考え方

まず、作ってみます。

定型です。


private struct MyEnvironmentKey: EnvironmentKey {
  static let defaultValue: String = "Default"
}

extension EnvironmentValues {
  var myCustomValue: String {
    get { self[MyEnvironmentKey.self] }
    set { self[MyEnvironmentKey.self] = newValue }
  }
}

extension View {
  func myCustomValue(_ myCustomValue: String) -> some View {
    environment(\.myCustomValue, myCustomValue)
  }
}

デフォルトに "Default" という文字列を設定していますが、

SwiftUI の View であればどこでもプロパティラッパーを経由して利用できます。

入れ子になった View で使ってみます。


struct Root: View {
  var body: some View {
    First()
    Second()
  }
}

struct First: View {
  @Environment(\.myCustomValue) var myCustomValue

  var body: some View {
    Text("First " + myCustomValue)
  }
}

struct Second: View {
  @Environment(\.myCustomValue) var myCustomValue

  var body: some View {
    Text("Secound " + myCustomValue)
    Third()
  }
}

struct Third: View {
  @Environment(\.myCustomValue) var myCustomValue

  var body: some View {
    Text("Third " + myCustomValue)
  }
}

「Scound」とか www

続いて、デフォルトの値を上書きして変更します。

Environment の値の変更時には、View のツリー構造を思い浮かべながら影響範囲を考えます。

変更箇所は一行追加のみです。

Enviroment 定義時に extension も作成していますので、それを使っても等価です。


struct Root: View {
  var body: some View {
    First()
    Second()
      .environment(\.myCustomValue, "Another")  // *
      //.myCustomValue("Another") // * extension
  }
}

struct First: View {
  @Environment(\.myCustomValue) var myCustomValue

  var body: some View {
    Text("First " + myCustomValue)
  }
}

struct Second: View {
  @Environment(\.myCustomValue) var myCustomValue

  var body: some View {
    Text("Secound " + myCustomValue)
    Third()
  }
}

struct Third: View {
  @Environment(\.myCustomValue) var myCustomValue

  var body: some View {
    Text("Third " + myCustomValue)
  }
}

Second() に付けた .environment() で更新しています。

影響範囲は「その下位の View すべて」となります。

SwiftUI には、あらかじめ用意された便利な Environment がたくさんあります。

その中から、さらに、加えて font を使ってみます。


var font: Font?
The default font of this environment.

👉 EnvironmentValues | Apple Developer Documentation hatena-bookmark

定義 (extension を含む) の記述は不要なので

ツリー構造だけ気にしながら、

好きなところですばやく上書きしていくことができます。


struct Root: View {

  var body: some View {
    First()
      .environment(\.font, .caption) // *
      //.font(.caption) // * extension
    Second()
      .environment(\.myCustomValue, "Another")
      //.myCustomValue("Another")
      .font(.largeTitle) // * extension
  }
}

struct First: View {
  @Environment(\.myCustomValue) var myCustomValue
  var body: some View {
    Text("First " + myCustomValue)
  }
}

struct Second: View {
  @Environment(\.myCustomValue) var myCustomValue
  var body: some View {
    Text("Secound " + myCustomValue)
    Third()
  }
}

struct Third: View {
  @Environment(\.myCustomValue) var myCustomValue
  var body: some View {
    Text("Third " + myCustomValue)
      .font(.caption) // * extension
  }
}

プロパティラッパー @Environment の利用は不要で、

下位 View に適用されていきます。

さらに、それを上書きもできます。

しかし、「Scound」とか恥ずいわ。

 

🌝 まとめ

ある程度調べてからやってみたのですが、少しイメージと違いました。

- デフォルト値はプロパティとしてどの View でも取得できる。

- 値の変更時に範囲を考慮しながら上書きする。

- プロパティで取得しなくてもそのまま適用されるものもある。

ちなみに、GitHub で調べてみると一番使われてる Built-in EnvironmentValue は、


@Environment(\.dismiss) var dismiss

でした。タイプは DismissAction です。

👉 Environment | Apple Developer Documentation hatena-bookmark
👉 EnvironmentValues | Apple Developer Documentation hatena-bookmark
👉 EnvironmentKey | Apple Developer Documentation hatena-bookmark


【SwiftUI】.frame(width:height:alignment:) を連続チェインするとどうなるか

そもそもは、

GeometryReader で alignment が壊れるのが嫌

で調べていただけなのですが。

「そいつのサイズを指定するときは .frame()」

ぐらいに思っていました。

👉 frame(width:height:alignment:) | Apple Developer Documentation hatena-bookmark

実験です。

どうなると思いますか。

まさかの

「連続ネスト」

でした。

しかも、VStack でも重なるとは。

よって、わたしのコードも更新していきます。


なんとなく「フレーム」というネーミングの意味が分かったような気がします。

実は分かってないけど。


【 Swift 】2次元配列と1次元配列の相互変換 🤔

こんな12年前の so の書き込み。


p.x = index / 3;
p.y = index % 3;

👉 c# - Convert 1D array index to 2D array index - Stack Overflow hatena-bookmark

さすが、先人先生。

やってみました。

長ったらしいですが考え方を思い出したいときのために。

 

🤔 配列をn個ずつに分割する

副産物としてこんなかんじに書けました。


extension Array {
  func chunked(by: Int) -> [[Element]] {
    return  (0 ..< (self.count / by)).map {
      Array(self[$0 * by ..< ($0 + 1) * by])
    }
  }
}

print(
  [1, 2, 3, 4, 5, 6].chunked(by: 3)
)
// [[1, 2, 3], [4, 5, 6]]

 

🤔 まとめ

配列の変換


let d2: [[String]] = [
  ["あ", "い", "う", "え", "お"],
  ["か", "き", "く", "け", "こ"],
  ["さ", "し", "す", "せ", "そ"]
]

// 2次元から1次元 *
let d1 = d2.flatMap { $0 }
print(d1)
// ["あ", "い", "う", "え", "お", "か", "き", "く", "け", "こ", "さ", "し", "す", "せ", "そ"]

print(
  // 1次元から2次元 *
  (0 ..< d1.count / numCols).map {
    Array(d1[($0 * numCols) ..< ($0 + 1) * numCols])
  }
)
// [["あ", "い", "う", "え", "お"], ["か", "き", "く", "け", "こ"], ["さ", "し", "す", "せ", "そ"]]

extension 化する。


extension Array where Element: Collection { 
  func toD1() -> [Element.Element] { // nested
    return self.flatMap { $0 } 
  }
}

extension Array {
  func toD2(numCols: Int) -> [[Element]] {
    return (0 ..< self.count / numCols).map {
      Array(self[($0 * numCols) ..< ($0 + 1) * numCols])
    }
  }
}

1次元配列インデックスと2次元配列座標の関係


index = y * numCols + x

x = index % numCols
y = index / numCols

SwiftData の inMemory で実装してみたが遅かったので、@Observable クラスへ。

👉 【 SwiftUI 】 Pong Wars を SwiftUI に移植してみた hatena-bookmark

しかし、正直、勉強すればするほど謎が増えます !


👉 【Swift】2次元配列 で 転置行列 ( transpose matrix ) hatena-bookmark