はじめに
SwiftUIで開発をしてると、データバインディングという言葉をよく聞くかもしれません。
データバインディングは、UIとデータの同期を自動で行う強力な機能です。
データバインディングを学ぶと他にも
「MVVM」「状態管理」「プロパティラッパー」等、様々なワードが関係してきます。
本記事は上記の説明だけでなく、
サンプルコードも紹介しますので参考になれば嬉しいです!
データバインディングとは?
データの状態を監視する仕組みを言います。
データとビューの間の双方向のデータフローを自動的に管理できたり、
SwiftUIではユーザ操作により画面の表示をリアルタイムで変更が可能になります!
データが変更される
→そのデータを参照しているUI要素も自動的に更新される
※逆に、UIから入力された値をデータに反映も可能!
MVVMとデータバインディングの関係
1. アーキテクチャを簡単にイメージしよう
アーキテクチャの種類にMVVMというのを聞いたことがありますでしょうか?
データバインディングではこのMVVMの理解も重要です。
2. MVVMとは?
SwiftUIでは、 MVVM(Model-View-ViewModel) がよく使われます。
MVVMは、以下の3つのコンポーネントに分かれます:
3. データバインディングとの関係は?
「View」と「ViewModel」の間で、データバインディングが重要な役割を果たします。
「View」は、「ViewModel」内のデータとバインドされます。
そしてViewModelがデータを更新するとViewも自動的に更新されます。
データバインディングには、状態管理が必要
データバインディングが効果的に機能するためには、状態管理が必要です。
SwiftUIでは、状態管理に関するプロパティがいくつか提供されています。
ここでプロパティラッパーという単語について触れます。
プロパティラッパーについて
プロパティに対し、なんらかの特定の動作や機能を提供するために
使える仕組みのことを言います。
いくつか例を挙げておきます。
具体的なプロパティラッパー
種類 | 説明 |
---|---|
State | ローカルの状態管理に使用されます。変更されるとviewが再描画されます。 |
Binding | 親viewから受け取ったデータをバインドするために使用します。子viewがこのデータを変更でき、その変更は親viewに反映されます。 |
ObservedObject | 外部のオブジェクトの状態を監視するために使用します。通常は、ViewModelとして利用します。 |
StateObject | viewが自身で管理するViewModelなどのオブジェクトに使用され、ライフサイクルを管理します。 |
EnvironmentObject | 複数view間で共有されるオブジェクトに使用します。グローバルな状態管理が必要な場合に便利です。 |
プロパティラッパーの導入と説明
SwiftUIにおけるプロパティラッパーは、データバインディングや状態管理を簡単にするための便利な機能です。上記で述べた各プロパティラッパーについて、さらに詳しく見ていきましょう。
State
Stateは、ビューがローカルに保持する状態を宣言します。
ローカルなデータが変更されるたびにビューが再描画されます。
struct ContentView: View {
// ローカルに「counter」を保持する
@State private var counter = 0
var body: some View {
VStack {
// 「counter」値を表示
Text("Counter: \(counter)")
// 「counter」値を1増やす
Button("Increment") {
counter += 1
}
}
}
}
Binding
Bindingは、親viewが管理する状態を子viewに渡すときに使います。
親viewの状態と子viewの状態が同期されます。
struct ContentView: View {
@State private var isOn = false
var body: some View {
ToggleView(isOn: $isOn)
}
}
struct ToggleView: View {
@Binding var isOn: Bool
// 状態が変わると、データが同期される
var body: some View {
Toggle("Switch", isOn: $isOn)
}
}
ObservedObject
クラスベースの外部オブジェクトの変更を監視します。
このオブジェクトは ObservableObject プロトコルを準拠しており、
状態が変更されたときにviewに通知されます。
class CounterModel: ObservableObject {
@Published var count = 0
}
struct ContentView: View {
@ObservedObject var model = CounterModel()
var body: some View {
VStack {
Text("Count: \(model.count)")
// 状態が変わると、viewに通知される
Button("Increment") {
model.count += 1
}
}
}
}
サンプルコード
これまでの内容を踏まえて、
データバインディングを活用したシンプルなカウンターアプリの例を見てみましょう。
class CounterModel: ObservableObject {
@Published var count = 0
}
struct ContentView: View {
// CounterModelを管理
@StateObject private var model = CounterModel()
var body: some View {
VStack {
// 「model」の値が記載される
Text("Count: \(model.count)")
.font(.largeTitle)
// 「model」の値を1増やす
Button("Increment") {
model.count += 1
}
.padding()
}
}
}
上記は「StateObject」を使って「CounterModel」を管理し、
カウントが増加するたびにviewが自動的に更新されます。
「StateObject」を宣言時に付与(wrap)しないと、
Cannot assign to property: 'self' is immutable
というエラーが発生します。
まとめ
SwiftUIのデータバインディングで、画面とデータをリアルタイムで同期できる仕組みを
理解いただけましたでしょうか?
MVVMやプロパティラッパーといった、
状態管理に関しても理解が深まると幸いです!