Swift・iOS

Swiftを中心に学んだことを記録に残すブログです。技術に関係ない記事もたまに書いています。

【SwiftUI】@Bindingの使い方

■Bindingとは

"A property wrapper type that can read and write a value owned by a source of truth."

※引用:Apple Developer Documentation

「信頼できる情報源が所有する値の読み取りと書き込みが可能なプロパティラッパータイプ」です。

 

・・・?

 

■サンプルコードを使って説明

以下の記事で使ったサンプルコードを引き続き使用して説明してみます。

www.hfoasi8fje3.work

 

該当コード

import SwiftUI

struct
ContentView: View { @State var buttonTitle = "Button sample" var body: some View { VStack { Button(action: { self.buttonTitle = "Button tapped!" }) { Text("\(buttonTitle)") .frame(width: 200, height: 70, alignment: .center) .foregroundColor(Color.white) .background(Color.blue) .cornerRadius(10, antialiased: true) } } } }

ボタンを選択すると以下の画像のように表示が切り替わります。

f:id:hfoasi8fje3:20201012220412p:plain

 

では、仮に同じボタンを2つ縦並びに表示して、それぞれ選択状態を管理するにはどのようにすればよいでしょうか。

単純にButtonを1つ増やしてみます。

import SwiftUI

struct ContentView: View {
    @State var topButtonTitle = "Button sample"
    @State var bottomButtonTitle = "Button sample"
    
    var body: some View {
        VStack {
            Button(action: {
                self.topButtonTitle = "Button tapped!"
            }) {
                Text("\(topButtonTitle)")
                    .frame(width: 200, height: 70, alignment: .center)
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                    .cornerRadius(10, antialiased: true)
            }
            .padding()
            
            Button(action: {
                self.bottomButtonTitle = "Button tapped!"
            }) {
                Text("\(bottomButtonTitle)")
                    .frame(width: 200, height: 70, alignment: .center)
                    .foregroundColor(Color.white)
                    .background(Color.blue)
                    .cornerRadius(10, antialiased: true)
            }
        }
    }
}

f:id:hfoasi8fje3:20201012220528p:plain

 

確かに、これでもそれぞれのボタンの選択状態を管理することができますが、ボタンの仕様が共通なのであれば、わざわざ似たような実装を繰り返すのではなく、ボタンのViewを2度インスタンス化してContentViewに表示したほうが無駄な実装を減らすことができそうです。以下のようなイメージです。

※以下のコードはエラーになるので、あくまでボタンを共通化したらよさそうというイメージだけ伝えたく記載しています。

import SwiftUI

struct ContentView: View {
    var body: some View {
        // SampleButtonを2度インスタンス化して縦並びで表示
        VStack {
            SampleButton()
                .padding()
SampleButton() } } } // Buttonの仕様が実装されたView struct SampleButton: View { @State var isTapped: Bool var body: some View { Button(action: { self.isTapped = true }) { Text(isTapped ? "Button tapped!" : "Button sample") .frame(width: 200, height: 70, alignment: .center) .foregroundColor(Color.white) .background(Color.blue) .cornerRadius(10, antialiased: true) } } }

 

ここで問題となるのが、「上部のボタンと下部のボタンでそれぞれボタンの選択状態を管理するにはどのようにすればよいか」です。

そこで登場するのが@Bindingです。@Bindingを使うと、他の構造体の変数と@Bindingをつけた変数を結びつけます(Binding=結びつけるという意味)。結びついた他の構造体の変数が状態を管理することで、インスタンスごとに状態を管理することができます。

以下が@Bindingを使って上部のボタンと下部のボタンでそれぞれボタンの選択状態を管理しているコードです。サンプルはButtonを使って説明しましたが、実際の使用ケースとしては、ログイン画面のIDとパスワードを入力するためのTextFieldなどが考えられます。

import SwiftUI

struct ContentView: View {
    // 追加
    // 上部のボタンの選択状態を管理する変数
    @State var isTopButtonTapped = false
    
    // 追加
    // 下部のボタンの選択状態を管理する変数
    @State var isBottomButtonTapped = false
    
    var body: some View {
        VStack {
            // 追加
            // インスタンス化する際に引数にisTopButtonTappedを指定したことで、
            // 構造体SampleButtonの変数isTappedの状態と、isTopButtonTappedの状態が結びつく
            // つまり、このインスタンスにおいてはisTappedがtrueになればisTopButtonTappedもtrueとなる
            SampleButton(isTapped: $isTopButtonTapped)
                .padding()
            
            // 追加
            // インスタンス化する際に引数にisBottomButtonTappedを指定したことで、
            // 構造体SampleButtonの変数isTappedの状態と、isBottomButtonTappedの状態が結びつく
            // つまり、このインスタンスにおいてはisTappedがtrueになればisBottomButtonTappedもtrueとなる
            SampleButton(isTapped: $isBottomButtonTapped)
        }
    }
}

 

■参考リンク

Apple Developer Documentation