SwiftUIの @State と @Binding を完全理解する入門記事

SwiftUI を使い始めると必ず出てくるのが @State@Binding。でも「struct なのに状態が残るのはなぜ?」「$マークの意味は?」と混乱しやすいポイントです。本記事では、初めて触る人でも理解できるように、概念・仕組み・具体例をわかりやすく解説します。


1. Viewはなぜ状態を持てない?

SwiftUI の View は struct です。struct は値型なので、再生成されると中身がリセットされます。つまり本来は「状態を保持する」ことができません。

struct MyStruct {
    var count = 0
}

var a = MyStruct()
a.count += 1      // → 1
a = MyStruct()    // → 0 にリセット

SwiftUI の View も同じで、再描画のたびに struct が作り直されます。これだとボタンを押してもカウントが保持できないはずです。


2. @State の役割

@State は「この View 内だけで使う、一時的な状態を保存する箱」です。SwiftUI が裏で専用ストレージを確保し、View が再生成されても値を再利用してくれる仕組みになっています。

例:

struct CounterView: View {
    @State private var count = 0   // 状態の箱

    var body: some View {
        VStack {
            Text("カウント: \(count)")
            Button("増やす") { count += 1 }
        }
    }
}
  • count は struct の中に直接保存されるわけではなく、SwiftUI が外部にストレージを持ちます。
  • 値が変わると View が再描画され、UI が自動で更新されます。

3. @Binding の役割

@Binding は「他の View が持っている @State を参照するひも」です。自分自身は状態を持たず、親ビューの状態を借りて操作できます。

struct ChildView: View {
    @Binding var count: Int   // 親の状態を参照

    var body: some View {
        Button("子ビューで増やす") {
            count += 1       // 親の count が増える
        }
    }
}

struct ParentView: View {
    @State private var count = 0   // 親が状態を保持

    var body: some View {
        VStack {
            Text("カウント: \(count)")
            ChildView(count: $count) // ← $をつけてバインディングを渡す
        }
    }
}
  • 親は @State で状態を持ちます。
  • 子は @Binding でその状態を参照します。
  • $count は「Binding(参照ひも)」を作って子に渡す記号です。

4. $(ドル記号)の意味

  • count → 値そのもの(Int)
  • $count → Binding(参照ひも)

例:

TextField("入力", text: $name)
  • 入力欄に文字を入れると name に即反映。
  • 逆に name の変更も TextField に反映。
  • 双方向の同期(双方向バインディング)が成立します。

5. 内部で何が起きているか

@State は実際には State<T> というラッパーです。概念的にはこう書けます:

@propertyWrapper
struct State<Value> {
    init(wrappedValue: Value)
    var wrappedValue: Value { get nonmutating set }
    var projectedValue: Binding<Value> { get }
}
  • wrappedValue → 実際の値(例: count)
  • projectedValue($count) → その値を共有する Binding

つまり、View が struct として再生成されても、SwiftUI が裏で同じストレージを再利用するので状態は保持されます。

図で表すと:

最初の描画 → ストレージ[count=0]
ボタン押下 → ストレージ[count=1]
再描画     → 新しいViewが作られるが、同じストレージ[count=1]を再利用

6. Environmentとの違い

  • @State → その View 内限定の状態
  • @Binding → 親からひもで状態を借りる
  • @Environment → アプリ全体や親ビューが提供する共通値を読む(例: colorScheme, modelContext

7. まとめ

  • @State: View 内の一時的な状態を保持。値が変われば自動で UI 再描画。
  • @Binding: 親の @State を参照する仕組み。子ビューから親の状態を操作できる。
  • $: @State から Binding(参照ひも)を取り出す記号。
  • View は struct なので本来状態を持てないが、SwiftUI が裏でストレージを持つため状態が残る。

8. 実例(フォーム画面)

struct EntryView: View {
    @State private var fact = ""
    @State private var interpretation = ""
    @State private var date = Date()
    @State private var showErr = false
    @State private var errMsg = ""
    @State private var savedToast = false

    var body: some View {
        VStack {
            TextField("事実", text: $fact)
            TextField("解釈", text: $interpretation)
            DatePicker("日付", selection: $date)

            Button("保存") {
                if fact.isEmpty {
                    errMsg = "事実が未入力です"
                    showErr = true
                } else {
                    savedToast = true
                }
            }
        }
        .alert(errMsg, isPresented: $showErr) {}
    }
}
  • fact, interpretation, date → 入力フォームの内容を保持。
  • showErr, errMsg → エラー用。
  • savedToast → 保存完了時の通知用。

すべてが @State で管理され、UI と自動同期します。

コメント

タイトルとURLをコピーしました