【SwiftUI】@Bindingで複数のビューで変数の値を紐づける

本記事では、複数のビューを持つアプリケーションにおいて、複数のビュー同士で変数の値をシェアし、別のビューで同一の変数を参照・更新する方法について解説していきます。

@Binding変数とは

@Binding変数とは、SwiftUIにおいてデータのやり取りを行う機能の一つで、別のビューで定義されている@State変数とデータを共有することができる変数です。

複数のビューを使用する場合に、呼び出し元のビューで@State変数を定義し、呼び出された側のビューで@Binding変数を用意することで同じデータを共有することができます。

データを共有すると表現しましたが、@State変数に入った値を@Binding変数にコピーして渡すのではなく、実際に@State変数に代入された値の保存場所を@Binding変数でも直接できるようになります。これを値の参照渡しと言います。

@Binding変数は@State変数から参照渡しをされることで、同じデータを参照することとなり、@Binding変数の値を変更した場合には@State変数の値が、@State変数の値を変更した場合には@Binding変数の値が更新されたような見え方になり、それぞれの変数のデータは常に同一となります。

今回は「リビング」「お風呂」「トイレ」「寝室」という各部屋のリストを作成し、電気のON/OFFの状態を管理するアプリケーションを作成しながら、@Bindingについて解説していきます。

2つ目のビューを作成する

まず、ContentViewの下に新たにLightOnというメソッドを作成し、中に@Binding変数を作成します。LightOnメソッドの中では画像を変数の値によって切り替える機能を作成します。

struct LightOn: View{
    @Binding var isChecked: Bool
    var body: some View{
        Image(systemName: isChecked ? "lightbulb.max.fill" :"lightbulb.max")
            .foregroundColor(isChecked ? .yellow : .black)
            .onTapGesture {
                isChecked.toggle()
            }
    }
}

isChecked変数には、ビューの呼び出し元から@State変数が渡され、その参照値のBool型のデータが入ります。

ImageのsystemNameの部分には、isCheckedの値がtrueであればlightbulb.max.fillという画像が、falseの場合には”lightbulb.max”が入るように設定します。これにより、呼び出し元の@State変数の値が変更されるたびにその内容を @Bindeing変数でも反映を認識できるようになります。

最後のonTapGestureはImage要素のクリックイベントを表しており、画像をクリックされるたびに行う処理を定義することができます。

isChecked.toggle()では、真偽値のisCheckedの値を現在の値とは逆の値に切り替えることができます。

初期表示

Imageクリック後

ビューを紐づける

画面のメイン要素となるContentViewで、先ほど作成したLightOnというViewを利用しながら計4つの真偽値の状態を管理し、状態によってViewの表示を切り替える方法を解説していきます。

ContentViewのソースコード

struct ContentView: View {
    @State var living: Bool = false
    @State var bath: Bool = false
    @State var toilet: Bool = false
    @State var bedroom: Bool = false
    var body: some View {
        if(living && bath && toilet && bedroom){
            Text("※全ての部屋の電気がついています。")
                .font(.title)
                .foregroundColor(.red)
        }else if(!living && !bath && !toilet && !bedroom){
            Text("※全ての部屋の電気が消えています。")
                .font(.title)
                .foregroundColor(.gray)
        }
        Grid{
            GridRow{
                Text("リビング").font(.title)
                LightOn(isChecked: $living)
                    .scaleEffect(3)
            }.frame(width: 150)
                .padding(50)
            GridRow{
                Text("お風呂").font(.title)
                LightOn(isChecked: $bath)
                    .scaleEffect(3)
            }.frame(width: 150)
                .padding(50)
            GridRow{
                Text("トイレ").font(.title)
                LightOn(isChecked: $toilet)
                    .scaleEffect(3)
            }.frame(width: 150)
                .padding(50)
            GridRow{
                Text("寝室").font(.title)
                LightOn(isChecked: $bedroom)
                    .scaleEffect(3)
            }.frame(width: 150)
                .padding(50)
        }
    }
}

まずは4つの部屋の電気の状態を管理するBool型変数を4つ用意し、初期値は全てfalseに設定します。

表示内容は、Gridを利用して「部屋名+電気アイコン」という行を縦に四つ並べるレイアウトとします。各行ではLightOn(isChecked: $living)ビューを利用して@State変数を渡して変数の参照を共有します。

GridRow{
                Text("リビング").font(.title)
                LightOn(isChecked: $living)
                    .scaleEffect(3)
            }.frame(width: 150)
                .padding(50)

最後に各@State変数の値をLightOnと共有し、変更内容を正確に検知できていることを確認するために全ての電気がONになっている状態、全ての電気がOFFになっている状態を条件として画面上部にテキストを表示する処理を入れています。

これにより、電球画像のクリックごとに即座にContentViewで状態を検知できて、状態に合わせた処理を実行できていることが確認できます。

全てON

全てOFF

最後に

本記事では、@Binding変数を利用して複数のビュー間で変数の値を共有する方法について解説してきました。

  • @Binding変数は別の呼び出し元Viewの@State変数と値の参照を共有することができる
  • @State変数は、呼び出し先の@Binding変数の値が変わった場合に即座にその変更が反映される

Instagramや旧Twitterなどのように画面下のタブを選択することで画面を切り替えることができるタブビューについても以下の記事で紹介していますのでぜひご覧ください!