【SwiftUI】@State を 子 View でどう受けるか 🤔 - @Binding

SwiftUI を使うには基本で必須です。

@State と @Binding。

どう書いてますか。

親 View があるとして 子 View でどう受けるか。


struct ParentView: View {
  @State private var text: String = ""

  var body: some View {
    VStack {

      TextField("Parent", text: $text)
      Text(text)

      Divider()

      ChildView(text: $text)

    }
    .padding()
  }
}


struct ChildView: View {
  var body: some View {
    TextField()
    Text()
  }
}

こういうイメージです。

きちんと Binding されていますね。

ChildView をシンプルに書いてみます。

あくまで受け渡し記述の確認です。

 

🤔 あれこれやってみる

他言語からきて、まずは書きそうなこれ。

イニシャライザ経由で型を合わせて渡します。

wrappedValue も直感的に分かるでしょう。


struct ChildView: View {
  private var text: Binding<String>

  init(text: Binding<String>) {
    self.text = text
  }

  var body: some View {
    TextField("Child", text: text)
    Text(text.wrappedValue)
  }
}

→ OK

次は、イニシャライザーを使わずに、プロパティを露出させます。


struct ChildView: View {
  var text: Binding<String>

  var body: some View {
    TextField("Child", text: text)
    Text(text.wrappedValue)
  }
}

→ OK

次は、マクロを使います。

@State で受けると、参照が切れます。新規に作成するんですね。


struct ChildView: View {
  @State private var text: String

  init(text: Binding<String>) {
    self.text = text.wrappedValue
  }

  var body: some View {
    TextField("Child", text: $text)
    Text(text)
  }
}

→ NG

次は、何も分かってないのに @Binding を使います。


struct ChildView: View {
  @Binding private var text: String

  init(text: Binding<String>) {
    self.text.projectedValue = text  // NG
  }

  var body: some View {
    TextField("Child", text: $text)
    Text(text)
  }
}


Referencing property 'projectedValue' requires wrapper 'Binding<String>'

→ NG

次は、少しネットで調べて、一番多く目についた書き方。

_ (アンダースコア) が強烈な違和感ですが、問題なく動きます。


struct ChildView: View {
  @Binding private var text: String

  init(text: Binding<String>) {
    _text = text
  }

  var body: some View {
    TextField("Child", text: $text)
    Text(text)
  }
}

→ OK

次は、public にして、イニシャライザー省略。


struct ChildView: View {
  @Binding var text: String

  var body: some View {
    TextField("Child", text: $text)
    Text(text)
  }
}

→ OK

 

🤔 まとめ

これが一番簡潔に書けるんですね!


struct ChildView: View {
  @Binding var text: String

  var body: some View {
    TextField("Child", text: $text)
    Text(text)
  }
}

子ではプロパティのアクセス修飾子を public のイニシャライザー省略で受ける。

どうやら、この


@Binding var text: String

は、


private var _text: Binding<String>
private var text: String {
  get {
    _text.wrappedValue
  }
  set {
    _text.wrappedValue = newValue
  }
}

init(text: Binding<String>) {
  _text = text
}

と等価のようにみえます。

あと、孫 View に渡すには、@Binding から @Binding できますね!

※ 後で知ったのですが、public や private などのアクセスレベルの省略は「internal」扱いでした !

👉 【Swift】「public」 を省略しない理由 🚫 hatena-bookmark

最後に、Playground コードを貼っておきます。



👉 【SwiftUI】再描画の伝播 - @State と @Binding hatena-bookmark


Jetpack Compose から SwiftUI に来ましたが今の謎をどうにかしたい 😩

Swift 初心者です。Kotlin からきました。

Apple 公式サンプルコードを3日間 ROM ってました。

👉 sample-backyard-birds/Multiplatform/Birds/BirdsSearchResults.swift at main · apple/sample-backyard-birds hatena-bookmark

どうも納得ができないので書き換えてみました。

動かしてみると3つとも特に問題ないような感じに見えました。

ネットで調べていると、どうもこちらも変化が激しいようで、どの記事を信じたらいいのか分かりません。

コードを見比べながら分からないこと、今後調べたいこと、を洗い出してみます。

 

😩 init()

ここで必要なのですか。なくても引数は同じ。


init(searchText: Binding<String>, @ViewBuilder content: @escaping (Bird) -> Content) {

パフォーマンス的な何か、なのでしょうか。

 

😩 KeyPath


_birds = Query(sort: \.creationDate)

すべて、Xcode 任せなのですが、省略できないんです。Bird が。

「Path」というぐらいなのでどこかに通せばいいと思っているのですが。

 

😩 Property Wrapper

いきなりでてくる _(アンダースコア)付きのこれ ROM 勢としては驚きました。

どこにもないのにいきなり登場してくる。

どこかに何か隠れてますか。


struct BirdsSearchResults<Content: View>: View {
  @Binding var searchText: String

  init(searchText: Binding<String>, ...) {
    _searchText = searchText

以下すべて挙動が同じに見えます。


struct BirdsSearchResults<Content: View>: View {
  @Binding var searchText: String


struct BirdsSearchResults<Content: View>: View {
  private var searchText: String

  init(searchText: Binding<String>, ...) {
    self.searchText = searchText.wrappedValue


struct BirdsSearchResults<Content: View>: View {
  private var searchText: Binding<String>

  init(searchText: Binding<String>, ...) {
    self.searchText = searchText

  var body: some View {
    let searchText = self.searchText.wrappedValue

ここの部分。


_searchText = searchText
_birds = Query(sort: \.creationDate)

以下記事で勉強したのですが。

👉 SwiftUI Property WrappersクラスのwrappedValue・projectedValue一覧表 #Swift - Qiita hatena-bookmark

隠しているものを丸出しに露出させることなどできるのでしょうか。

 

😩 $0

この記述よく見かけます。


birds.filter {
  $0.speciesName.contains(

分かりづらい感じがしますが、なぜ具体的なものに置き換えないのでしょうか。

Kotlin でも同様な記述があり、よく怒られていました。

 

😩 まとめ

対象の Apple サンプルコードは WWDC2023 のものなので約1年前ぐらいで、そんなに古くはないと思っています。

どうかどうかよろしくおねがいします。



【Xcode】Auto-Completion がおかしい 不具合の理由 → DerivedData

なぜか、

通常プロジェクトと Playground では Auto-Completion が違う。

マルチなクロージャーを含む


Button(action:label:)

を Auto-Completion で生成して、全クロージャー展開しようとするとできない。

せっかく Playgorund で Completion の使い方を掴んだような気がしてたのに!


 

🛠️ 症状

クロージャー展開しようとすると以下の感じ。

Playground 側は OK。


// Playground

Button {
  code
} label: {
  code
}

// Button(action: <#T##() -> Void#>, label: <#T##() -> View#>)

フォーカスの当たったままコピーすると設定記述的なものがテキストで取得できる。

これが、既存プロジェクト側では、なぜか、展開できない。


// Project

Button(action: {}, label: {
        Text("Button")
 })

// Button(action: /*@START_MENU_TOKEN@*/{}/*@END_MENU_TOKEN@*/, label: {
//   /*@START_MENU_TOKEN@*/Text("Button")/*@END_MENU_TOKEN@*/
//    })

なんやこれ。

プロジェクト内に同様な記述があると思って探しみたけど見当たらない。

登録されている Completions を


Editor
  ↓
Show Completions

で確認しても問題はなさそう。

あれこれやっていると、直ることがある。


- Xcode 再起動直後だけ一瞬直る。
- しかし、2回目ぐらいから戻っておかしくなる。

よって、キャッシュか何かを読み込んでいる、と想像。

 

🛠️ 修正・対応方法

これでいけた。


rm -rf ~/Library/Developer/Xcode/DerivedData/*

👉 Xcode Quick Fix - Clear Cache hatena-bookmark

以下でも直ったのかもしれない。


Product

  ↓

Clean Build Folder...

  +

Option


👉 [Xcode][小ネタ] DerivedDataの削除についての備忘録 | DevelopersIO hatena-bookmark

 

🛠️ まとめ

DerivedData を削除することで効果が出る不具合。


- Build Failed
- ストレージが肥大
- Completion がおかしい

キャッシュのようなもので消しても問題ないらしい。

場所はデフォルトで、


~/Library/Developer/Xcode/DerivedData

で以下メニューから確認できる。


File

 ↓

{project name} Settings...

少し前に、Xcode をバージョンアップデートしたのでそれが影響しているのかもしれないです。