見出し画像

SwiftUIでデータバインディング

データバインディングとは?

データバインディングとは、データと対象を結びつけて、データまたは対象の変更を暗示的にもう片方にに反映させること。例えば、アプリ上のUIを操作すると、その変更がデータに反映されたり、データを更新するとUIも再描画される。

SwiftUIでデータバインディング

SwiftUIのデータバインディングの仕組み:

@State:プロパティとViewを紐付ける
@ObservedObject:データクラスとViewを紐付ける(Viewの再構築でインスタンスが破棄される)
@StateObject:データクラスとViewを紐付ける(Viewの再構築でもインスタンスが破棄されない)
@EnvironmentObject:データクラスとViewを紐付ける(複数Viewやアプリケーション全体に渡ってデータを共有できる)

@State

SwiftUIのViewはstructなので、保持するプロパティを変更できないが、@Stateを付けたプロパティは、SwiftUIフレームワークによって監視され、プロパティの値とUIの状態が同期される。

ただし、そのプロパティは宣言されたView内からアクセスできないので、子Viewからは@Bindingでプロパティを宣言すると参照でき、値の更新も親Viewに反映される。

struct ContentView: View {
   // ON/OFFの状態をプロパティで保持する
   @State private var isOn = false

   var body: some View {
       VStack {
           // isOnの参照を渡して、スイッチのON/OFF時にisOnが更新される
           Toggle(isOn: $isOn) {
               Text("設定")
           }
           
           // isOnの値で表示テキストを変更
           Text(isOn ? "設定ON" : "設定OFF")
       }
   }
}
// 親View
struct ContentView: View {
   @State private var isOn = false

   var body: some View {
       VStack {
           // 子ViewにisOnの参照を渡す
           SwitchView(isOn: $isOn)
           Text(isOn ? "設定ON" : "設定OFF")
       }
   }
}

// 子View
struct SwitchView: View {
   // 親Viewのプロパティへの参照を宣言
   @Binding var isOn: Bool
   
   var body: some View {
       // 子ViewでisOnが変更されれば、親ViewのisOnも変更される
       Toggle(isOn: $isOn) {
           Text("設定")
       }
   }
}

@ObservedObject

プロパティではなくデータクラス(classインスタンス)とViewを紐付ける。インスタンスのプロパティの変化を監視し、値が変化するとViewが再描画される。しかし、Viewが再構築されると、インスタンスが初期化されてしまう。

対象クラスは
・ObservableObjectプロトコルに準拠する必要あり
・監視対象のプロパティは@Published属性を付与する必要あり
struct ContentView: View {
   // バインドしたいデータクラスのインスタンスを@ObservedObjectで宣言
   @ObservedObject var viewModel = ViewModel()

   var body: some View {
       VStack {
           Toggle(isOn: $viewModel.isOn) {
               Text("設定")
           }
           
           Text(viewModel.isOn ? "設定ON" : "設定OFF")
       }
   }
}

// データクラス:ObservableObjectプロトコルに準拠
class ViewModel: ObservableObject {
   // バインドしたいプロパティを@Publishedで宣言
   @Published var isOn = true
}

@StateObject

@ObservedObjectと同様に、データクラスとViewを紐付けるが、Viewが再構築されてもデータクラスのインスタンスが初期化されずに保持される。

// 親View
struct ContentView: View {
   // バインドしたいデータクラスのインスタンスを@StateObjectで宣言
   @StateObject var viewModel = ViewModel()

   var body: some View {
       VStack {
           SwitchView(viewModel: viewModel)
           Text(viewModel.isOn ? "設定ON" : "設定OFF")
       }
   }
}

// 子View
struct SwitchView: View {
   // 親Viewから渡されるので@ObservedObjectで宣言
   @ObservedObject var viewModel: ViewModel

   var body: some View {
       Toggle(isOn: $viewModel.isOn) {
           Text("設定")
       }
   }
}

class ViewModel: ObservableObject {
   @Published var isOn = true
}

@EnvironmentObject

@ObservedObjectと同様に、データクラスとViewを紐付けるが、子Viewに渡さなくても配下のViewからアクセスできる。階層トップのViewに紐付けると、アプリケーション全体からアクセスできるので、アプリケーション全体でデータ共有が必要な場合に使える。

// 親View
struct ContentView: View {
   // 共有するデータクラスのインスタンスを宣言
   @EnvironmentObject var setting: Setting

   var body: some View {
       VStack {
           SwitchView()
           Text(setting.isOn ? "設定ON" : "設定OFF")
       }
   }
}

// 子View
struct SwitchView: View {
   // 共有するデータクラスのインスタンスを宣言
   @EnvironmentObject var switchSetting: Setting

   var body: some View {
       // インスタンスを共有しているので、ここで変更されると親Viewにも反映される
       Toggle(isOn: $switchSetting.isOn) {
           Text("設定")
       }
   }
}

// データクラス:ObservableObjectプロトコルに準拠
class Setting: ObservableObject {
   @Published var isOn = true
}
@main
struct testApp: App {
   var body: some Scene {
       WindowGroup {
           // データクラスのインスタンスをバインドする
           ContentView().environmentObject(Setting())
       }
   }
}

参考


この記事が気に入ったらサポートをしてみませんか?