【#SwiftUI】レイアウトの調整に View.background(Color.*) が便利だな

もっと、いい方法があれば教えてほしいです。

連続する Modifirer の中で

パディング、マージンの調整に便利かも、

という発想。

視覚的に把握できるの便利。

View Debugger で見るより実はシンプルで速い。



慣れてしまえばどうでもいい話かな。


【SwiftUI】View プロパティの記述

これ、みんなフツーに書いてるけど。

これだけ。


struct ToggleView: View {
  @State var isOn = true

  var body: some View {
    Toggle(isOn ? "ON" : "OFF", isOn: $isOn)
  }
}

スゴイよね、SwiftUI。

 

📱 プロパティの記述

これよ。


@State var isOn = true

プロパティの記述をざっくり考えると、


[@State] [public|private] {let|var} isOn[: Bool] [= true]

となり、数十通りあるのに!

いくつか書き出して試してみます。


let isOn: Bool
let isOn: Bool = true
var isOn: Bool 
var isOn: Bool = true
private let isOn: Bool
private let isOn: Bool = true
private var isOn: Bool 
private var isOn: Bool = true
@State let isOn: Bool
@State let isOn: Bool = true
@State var isOn: Bool 
@State var isOn: Bool = true 
@State private let isOn: Bool
@State private let isOn: Bool = true
@State private var isOn: Bool 
@State private var isOn: Bool = true

 

📱 結果


// Cannot find '$isOn' in scope

let isOn: Bool
let isOn: Bool = true
var isOn: Bool 
var isOn: Bool = true
private let isOn: Bool
private let isOn: Bool = true
private var isOn: Bool 
private var isOn: Bool = true

→ Toggle() の2番目の引数 $isOn が見つからない。


// Property wrapper can only be applied to a 'var'

@State let isOn: Bool
@State let isOn: Bool = true
@State private let isOn: Bool
@State private let isOn: Bool = true

→ @State には var しか使えない。


// Missing argument for parameter 'isOn' in call

@State var isOn: Bool

→ isOn の中身がない。


// Missing argument for parameter 'isOn' in call
// 'ToggleView' initializer is inaccessible due to 'private' protection level

@State private var isOn: Bool

→ isOn の中身がない。
→ private なのでイニシャライザーがアクセスできません。


// OK

@State var isOn: Bool = true
@State private var isOn: Bool = true

→ OK

 

📱 まとめ

Property wrapper は var 。

中身が変わるからかな。



【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